diff --git a/CommandLine.cs b/CommandLine.cs
index b45b274..ee4cabf 100644
--- a/CommandLine.cs
+++ b/CommandLine.cs
@@ -909,7 +909,7 @@ namespace WPinternals
// Save supported mobilestartup.efi
LogFile.Log("Taking mobilestartup.efi from donor-FFU");
- Stream MobileStartupStream = UnlockedEFIESPFileSystem.OpenFile(@"Windows\System32\Boot\mobilestartup.efi", FileMode.Create, FileAccess.Write);
+ Stream MobileStartupStream = UnlockedEFIESPFileSystem.OpenFile(@"\Windows\System32\Boot\mobilestartup.efi", FileMode.Create, FileAccess.Write);
MobileStartupStream.Write(SupportedMobileStartup, 0, SupportedMobileStartup.Length);
MobileStartupStream.Close();
}
@@ -923,7 +923,7 @@ namespace WPinternals
// Edit BCD
LogFile.Log("Edit BCD");
- using Stream BCDFileStream = UnlockedEFIESPFileSystem.OpenFile(@"efi\Microsoft\Boot\BCD", FileMode.Open, FileAccess.ReadWrite);
+ using Stream BCDFileStream = UnlockedEFIESPFileSystem.OpenFile(@"\efi\Microsoft\Boot\BCD", FileMode.Open, FileAccess.ReadWrite);
using DiscUtils.Registry.RegistryHive BCDHive = new(BCDFileStream);
DiscUtils.BootConfig.Store BCDStore = new(BCDHive.Root);
DiscUtils.BootConfig.BcdObject MobileStartupObject = BCDStore.GetObject(new Guid("{01de5a27-8705-40db-bad6-96fa5187d4a6}"));
@@ -997,7 +997,7 @@ namespace WPinternals
// Save supported mobilestartup.efi
LogFile.Log("Taking mobilestartup.efi from donor-FFU");
- Stream MobileStartupStream = UnlockedEFIESPFileSystem.OpenFile(@"Windows\System32\Boot\mobilestartup.efi", FileMode.Create, FileAccess.Write);
+ Stream MobileStartupStream = UnlockedEFIESPFileSystem.OpenFile(@"\Windows\System32\Boot\mobilestartup.efi", FileMode.Create, FileAccess.Write);
MobileStartupStream.Write(SupportedMobileStartup, 0, SupportedMobileStartup.Length);
MobileStartupStream.Close();
}
@@ -1011,7 +1011,7 @@ namespace WPinternals
// Edit BCD
LogFile.Log("Edit BCD");
- using Stream BCDFileStream = UnlockedEFIESPFileSystem.OpenFile(@"efi\Microsoft\Boot\BCD", FileMode.Open, FileAccess.ReadWrite);
+ using Stream BCDFileStream = UnlockedEFIESPFileSystem.OpenFile(@"\efi\Microsoft\Boot\BCD", FileMode.Open, FileAccess.ReadWrite);
using DiscUtils.Registry.RegistryHive BCDHive = new(BCDFileStream);
DiscUtils.BootConfig.Store BCDStore = new(BCDHive.Root);
DiscUtils.BootConfig.BcdObject MobileStartupObject = BCDStore.GetObject(new Guid("{01de5a27-8705-40db-bad6-96fa5187d4a6}"));
diff --git a/DiscUtils/DiscUtils.Core/CoreCompat/EncodingHelper.cs b/DiscUtils/DiscUtils.Core/CoreCompat/EncodingHelper.cs
new file mode 100644
index 0000000..4060ca2
--- /dev/null
+++ b/DiscUtils/DiscUtils.Core/CoreCompat/EncodingHelper.cs
@@ -0,0 +1,23 @@
+#if !NET40 && !NET45
+using System.Text;
+#endif
+
+namespace DiscUtils.CoreCompat
+{
+ internal static class EncodingHelper
+ {
+ private static bool _registered;
+
+ public static void RegisterEncodings()
+ {
+ if (_registered)
+ return;
+
+ _registered = true;
+
+#if !NET40 && !NET45
+ Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
+#endif
+ }
+ }
+}
\ No newline at end of file
diff --git a/DiscUtils/DiscUtils.Core/Internal/Utilities.cs b/DiscUtils/DiscUtils.Core/Internal/Utilities.cs
new file mode 100644
index 0000000..dc042bc
--- /dev/null
+++ b/DiscUtils/DiscUtils.Core/Internal/Utilities.cs
@@ -0,0 +1,467 @@
+//
+// Copyright (c) 2008-2011, Kenneth Bell
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the "Software"),
+// to deal in the Software without restriction, including without limitation
+// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+// and/or sell copies of the Software, and to permit persons to whom the
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using System.Text.RegularExpressions;
+
+namespace DiscUtils.Internal
+{
+ internal static class Utilities
+ {
+ ///
+ /// Converts between two arrays.
+ ///
+ /// The type of the elements of the source array.
+ /// The type of the elements of the destination array.
+ /// The source array.
+ /// The function to map from source type to destination type.
+ /// The resultant array.
+ public static U[] Map(ICollection source, Func func)
+ {
+ U[] result = new U[source.Count];
+ int i = 0;
+
+ foreach (T sVal in source)
+ {
+ result[i++] = func(sVal);
+ }
+
+ return result;
+ }
+
+ ///
+ /// Converts between two arrays.
+ ///
+ /// The type of the elements of the source array.
+ /// The type of the elements of the destination array.
+ /// The source array.
+ /// The function to map from source type to destination type.
+ /// The resultant array.
+ public static U[] Map(IEnumerable source, Func func)
+ {
+ List result = new List();
+
+ foreach (T sVal in source)
+ {
+ result.Add(func(sVal));
+ }
+
+ return result.ToArray();
+ }
+
+ ///
+ /// Filters a collection into a new collection.
+ ///
+ /// The type of the new collection.
+ /// The type of the collection entries.
+ /// The collection to filter.
+ /// The predicate to select which entries are carried over.
+ /// The new collection, containing all entries where the predicate returns true.
+ public static C Filter(ICollection source, Func predicate) where C : ICollection, new()
+ {
+ C result = new C();
+ foreach (T val in source)
+ {
+ if (predicate(val))
+ {
+ result.Add(val);
+ }
+ }
+
+ return result;
+ }
+
+ ///
+ /// Indicates if two ranges overlap.
+ ///
+ /// The type of the ordinals.
+ /// The lowest ordinal of the first range (inclusive).
+ /// The highest ordinal of the first range (exclusive).
+ /// The lowest ordinal of the second range (inclusive).
+ /// The highest ordinal of the second range (exclusive).
+ /// true if the ranges overlap, else false.
+ public static bool RangesOverlap(T xFirst, T xLast, T yFirst, T yLast) where T : IComparable
+ {
+ return !((xLast.CompareTo(yFirst) <= 0) || (xFirst.CompareTo(yLast) >= 0));
+ }
+
+ #region Bit Twiddling
+
+ public static bool IsAllZeros(byte[] buffer, int offset, int count)
+ {
+ int end = offset + count;
+ for (int i = offset; i < end; ++i)
+ {
+ if (buffer[i] != 0)
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ public static bool IsPowerOfTwo(uint val)
+ {
+ if (val == 0)
+ {
+ return false;
+ }
+
+ while ((val & 1) != 1)
+ {
+ val >>= 1;
+ }
+
+ return val == 1;
+ }
+
+ public static bool IsPowerOfTwo(long val)
+ {
+ if (val == 0)
+ {
+ return false;
+ }
+
+ while ((val & 1) != 1)
+ {
+ val >>= 1;
+ }
+
+ return val == 1;
+ }
+
+ public static bool AreEqual(byte[] a, byte[] b)
+ {
+ if (a.Length != b.Length)
+ {
+ return false;
+ }
+
+ for (int i = 0; i < a.Length; ++i)
+ {
+ if (a[i] != b[i])
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ public static ushort BitSwap(ushort value)
+ {
+ return (ushort)(((value & 0x00FF) << 8) | ((value & 0xFF00) >> 8));
+ }
+
+ public static uint BitSwap(uint value)
+ {
+ return ((value & 0xFF) << 24) | ((value & 0xFF00) << 8) | ((value & 0x00FF0000) >> 8) |
+ ((value & 0xFF000000) >> 24);
+ }
+
+ public static ulong BitSwap(ulong value)
+ {
+ return ((ulong)BitSwap((uint)(value & 0xFFFFFFFF)) << 32) | BitSwap((uint)(value >> 32));
+ }
+
+ public static short BitSwap(short value)
+ {
+ return (short)BitSwap((ushort)value);
+ }
+
+ public static int BitSwap(int value)
+ {
+ return (int)BitSwap((uint)value);
+ }
+
+ public static long BitSwap(long value)
+ {
+ return (long)BitSwap((ulong)value);
+ }
+
+ #endregion
+
+ #region Path Manipulation
+
+ ///
+ /// Extracts the directory part of a path.
+ ///
+ /// The path to process.
+ /// The directory part.
+ public static string GetDirectoryFromPath(string path)
+ {
+ string trimmed = path.TrimEnd('\\');
+
+ int index = trimmed.LastIndexOf('\\');
+ if (index < 0)
+ {
+ return string.Empty; // No directory, just a file name
+ }
+
+ return trimmed.Substring(0, index);
+ }
+
+ ///
+ /// Extracts the file part of a path.
+ ///
+ /// The path to process.
+ /// The file part of the path.
+ public static string GetFileFromPath(string path)
+ {
+ string trimmed = path.Trim('\\');
+
+ int index = trimmed.LastIndexOf('\\');
+ if (index < 0)
+ {
+ return trimmed; // No directory, just a file name
+ }
+
+ return trimmed.Substring(index + 1);
+ }
+
+ ///
+ /// Combines two paths.
+ ///
+ /// The first part of the path.
+ /// The second part of the path.
+ /// The combined path.
+ public static string CombinePaths(string a, string b)
+ {
+ if (string.IsNullOrEmpty(a) || (b.Length > 0 && b[0] == '\\'))
+ {
+ return b;
+ }
+ if (string.IsNullOrEmpty(b))
+ {
+ return a;
+ }
+ return a.TrimEnd('\\') + '\\' + b.TrimStart('\\');
+ }
+
+ ///
+ /// Resolves a relative path into an absolute one.
+ ///
+ /// The base path to resolve from.
+ /// The relative path.
+ /// The absolute path. If no is specified
+ /// then relativePath is returned as-is. If
+ /// contains more '..' characters than the base path contains levels of
+ /// directory, the resultant string be the root drive followed by the file name.
+ /// If no the basePath starts with '\' (no drive specified) then the returned
+ /// path will also start with '\'.
+ /// For example: (\TEMP\Foo.txt, ..\..\Bar.txt) gives (\Bar.txt).
+ ///
+ public static string ResolveRelativePath(string basePath, string relativePath)
+ {
+ if (string.IsNullOrEmpty(basePath))
+ {
+ return relativePath;
+ }
+
+ if (!basePath.EndsWith(@"\"))
+ basePath = Path.GetDirectoryName(basePath);
+
+ string merged = Path.GetFullPath(Path.Combine(basePath, relativePath));
+
+ if (basePath.StartsWith(@"\") && merged.Length > 2 && merged[1].Equals(':'))
+ {
+ return merged.Substring(2);
+ }
+
+ return merged;
+ }
+
+ public static string ResolvePath(string basePath, string path)
+ {
+ if (!path.StartsWith("\\", StringComparison.OrdinalIgnoreCase))
+ {
+ return ResolveRelativePath(basePath, path);
+ }
+ return path;
+ }
+
+ public static string MakeRelativePath(string path, string basePath)
+ {
+ List pathElements =
+ new List(path.Split(new[] { '\\' }, StringSplitOptions.RemoveEmptyEntries));
+ List basePathElements =
+ new List(basePath.Split(new[] { '\\' }, StringSplitOptions.RemoveEmptyEntries));
+
+ if (!basePath.EndsWith("\\", StringComparison.Ordinal) && basePathElements.Count > 0)
+ {
+ basePathElements.RemoveAt(basePathElements.Count - 1);
+ }
+
+ // Find first part of paths that don't match
+ int i = 0;
+ while (i < Math.Min(pathElements.Count - 1, basePathElements.Count))
+ {
+ if (pathElements[i].ToUpperInvariant() != basePathElements[i].ToUpperInvariant())
+ {
+ break;
+ }
+
+ ++i;
+ }
+
+ // For each remaining part of the base path, insert '..'
+ StringBuilder result = new StringBuilder();
+ if (i == basePathElements.Count)
+ {
+ result.Append(@".\");
+ }
+ else if (i < basePathElements.Count)
+ {
+ for (int j = 0; j < basePathElements.Count - i; ++j)
+ {
+ result.Append(@"..\");
+ }
+ }
+
+ // For each remaining part of the path, add the path element
+ for (int j = i; j < pathElements.Count - 1; ++j)
+ {
+ result.Append(pathElements[j]);
+ result.Append(@"\");
+ }
+
+ result.Append(pathElements[pathElements.Count - 1]);
+
+ // If the target was a directory, put the terminator back
+ if (path.EndsWith(@"\", StringComparison.Ordinal))
+ {
+ result.Append(@"\");
+ }
+
+ return result.ToString();
+ }
+
+ #endregion
+
+ #region Filesystem Support
+
+ ///
+ /// Indicates if a file name matches the 8.3 pattern.
+ ///
+ /// The name to test.
+ /// true if the name is 8.3, otherwise false.
+ public static bool Is8Dot3(string name)
+ {
+ if (name.Length > 12)
+ {
+ return false;
+ }
+
+ string[] split = name.Split('.');
+
+ if (split.Length > 2 || split.Length < 1)
+ {
+ return false;
+ }
+
+ if (split[0].Length > 8)
+ {
+ return false;
+ }
+
+ foreach (char ch in split[0])
+ {
+ if (!Is8Dot3Char(ch))
+ {
+ return false;
+ }
+ }
+
+ if (split.Length > 1)
+ {
+ if (split[1].Length > 3)
+ {
+ return false;
+ }
+
+ foreach (char ch in split[1])
+ {
+ if (!Is8Dot3Char(ch))
+ {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ public static bool Is8Dot3Char(char ch)
+ {
+ return (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') || "_^$~!#%£-{}()@'`&".IndexOf(ch) != -1;
+ }
+
+ ///
+ /// Converts a 'standard' wildcard file/path specification into a regular expression.
+ ///
+ /// The wildcard pattern to convert.
+ /// The resultant regular expression.
+ ///
+ /// The wildcard * (star) matches zero or more characters (including '.'), and ?
+ /// (question mark) matches precisely one character (except '.').
+ ///
+ public static Regex ConvertWildcardsToRegEx(string pattern)
+ {
+ if (!pattern.Contains("."))
+ {
+ pattern += ".";
+ }
+
+ string query = "^" + Regex.Escape(pattern).Replace(@"\*", ".*").Replace(@"\?", "[^.]") + "$";
+ return new Regex(query, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
+ }
+
+ public static FileAttributes FileAttributesFromUnixFileType(UnixFileType fileType)
+ {
+ switch (fileType)
+ {
+ case UnixFileType.Fifo:
+ return FileAttributes.Device | FileAttributes.System;
+ case UnixFileType.Character:
+ return FileAttributes.Device | FileAttributes.System;
+ case UnixFileType.Directory:
+ return FileAttributes.Directory;
+ case UnixFileType.Block:
+ return FileAttributes.Device | FileAttributes.System;
+ case UnixFileType.Regular:
+ return FileAttributes.Normal;
+ case UnixFileType.Link:
+ return FileAttributes.ReparsePoint;
+ case UnixFileType.Socket:
+ return FileAttributes.Device | FileAttributes.System;
+ default:
+ return 0;
+ }
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/DiscUtils/DiscUtils.Fat/ClusterReader.cs b/DiscUtils/DiscUtils.Fat/ClusterReader.cs
new file mode 100644
index 0000000..7831056
--- /dev/null
+++ b/DiscUtils/DiscUtils.Fat/ClusterReader.cs
@@ -0,0 +1,95 @@
+//
+// Copyright (c) 2008-2011, Kenneth Bell
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the "Software"),
+// to deal in the Software without restriction, including without limitation
+// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+// and/or sell copies of the Software, and to permit persons to whom the
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.IO;
+using DiscUtils.Streams;
+
+namespace DiscUtils.Fat
+{
+ internal sealed class ClusterReader
+ {
+ private readonly int _bytesPerSector;
+
+ ///
+ /// Pre-calculated value because of number of uses of this externally.
+ ///
+ private readonly int _clusterSize;
+
+ private readonly int _firstDataSector;
+ private readonly int _sectorsPerCluster;
+ private readonly Stream _stream;
+
+ public ClusterReader(Stream stream, int firstDataSector, int sectorsPerCluster, int bytesPerSector)
+ {
+ _stream = stream;
+ _firstDataSector = firstDataSector;
+ _sectorsPerCluster = sectorsPerCluster;
+ _bytesPerSector = bytesPerSector;
+
+ _clusterSize = _sectorsPerCluster * _bytesPerSector;
+ }
+
+ public int ClusterSize
+ {
+ get { return _clusterSize; }
+ }
+
+ public void ReadCluster(uint cluster, byte[] buffer, int offset)
+ {
+ if (offset + ClusterSize > buffer.Length)
+ {
+ throw new ArgumentOutOfRangeException(nameof(offset),
+ "buffer is too small - cluster would overflow buffer");
+ }
+
+ uint firstSector = (uint)((cluster - 2) * _sectorsPerCluster + _firstDataSector);
+
+ _stream.Position = firstSector * _bytesPerSector;
+ StreamUtilities.ReadExact(_stream, buffer, offset, _clusterSize);
+ }
+
+ internal void WriteCluster(uint cluster, byte[] buffer, int offset)
+ {
+ if (offset + ClusterSize > buffer.Length)
+ {
+ throw new ArgumentOutOfRangeException(nameof(offset),
+ "buffer is too small - cluster would overflow buffer");
+ }
+
+ uint firstSector = (uint)((cluster - 2) * _sectorsPerCluster + _firstDataSector);
+
+ _stream.Position = firstSector * _bytesPerSector;
+
+ _stream.Write(buffer, offset, _clusterSize);
+ }
+
+ internal void WipeCluster(uint cluster)
+ {
+ uint firstSector = (uint)((cluster - 2) * _sectorsPerCluster + _firstDataSector);
+
+ _stream.Position = firstSector * _bytesPerSector;
+
+ _stream.Write(new byte[_clusterSize], 0, _clusterSize);
+ }
+ }
+}
\ No newline at end of file
diff --git a/DiscUtils/DiscUtils.Fat/ClusterStream.cs b/DiscUtils/DiscUtils.Fat/ClusterStream.cs
new file mode 100644
index 0000000..310b291
--- /dev/null
+++ b/DiscUtils/DiscUtils.Fat/ClusterStream.cs
@@ -0,0 +1,464 @@
+//
+// Copyright (c) 2008-2011, Kenneth Bell
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the "Software"),
+// to deal in the Software without restriction, including without limitation
+// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+// and/or sell copies of the Software, and to permit persons to whom the
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+
+namespace DiscUtils.Fat
+{
+ internal class ClusterStream : Stream
+ {
+ private readonly FileAccess _access;
+ private readonly byte[] _clusterBuffer;
+ private readonly FileAllocationTable _fat;
+
+ private readonly List _knownClusters;
+ private readonly ClusterReader _reader;
+
+ private bool _atEOF;
+
+ private uint _currentCluster;
+ private uint _length;
+ private long _position;
+
+ internal ClusterStream(FatFileSystem fileSystem, FileAccess access, uint firstCluster, uint length)
+ {
+ _access = access;
+ _reader = fileSystem.ClusterReader;
+ _fat = fileSystem.Fat;
+ _length = length;
+
+ _knownClusters = new List();
+ if (firstCluster != 0)
+ {
+ _knownClusters.Add(firstCluster);
+ }
+ else
+ {
+ _knownClusters.Add(FatBuffer.EndOfChain);
+ }
+
+ if (_length == uint.MaxValue)
+ {
+ _length = DetectLength();
+ }
+
+ _currentCluster = uint.MaxValue;
+ _clusterBuffer = new byte[_reader.ClusterSize];
+ }
+
+ public override bool CanRead
+ {
+ get { return _access == FileAccess.Read || _access == FileAccess.ReadWrite; }
+ }
+
+ public override bool CanSeek
+ {
+ get { return true; }
+ }
+
+ public override bool CanWrite
+ {
+ get { return _access == FileAccess.ReadWrite || _access == FileAccess.Write; }
+ }
+
+ public override long Length
+ {
+ get { return _length; }
+ }
+
+ public override long Position
+ {
+ get { return _position; }
+
+ set
+ {
+ if (value >= 0)
+ {
+ _position = value;
+ _atEOF = false;
+ }
+ else
+ {
+ throw new ArgumentOutOfRangeException(nameof(value), "Attempt to move before beginning of stream");
+ }
+ }
+ }
+
+ public event FirstClusterChangedDelegate FirstClusterChanged;
+
+ public override void Flush() { }
+
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+ if (!CanRead)
+ {
+ throw new IOException("Attempt to read from file not opened for read");
+ }
+
+ if (_position > _length)
+ {
+ throw new IOException("Attempt to read beyond end of file");
+ }
+
+ if (count < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(count), "Attempt to read negative number of bytes");
+ }
+
+ int target = count;
+ if (_length - _position < count)
+ {
+ target = (int)(_length - _position);
+ }
+
+ if (!TryLoadCurrentCluster())
+ {
+ if ((_position == _length || _position == DetectLength()) && !_atEOF)
+ {
+ _atEOF = true;
+ return 0;
+ }
+ throw new IOException("Attempt to read beyond known clusters");
+ }
+
+ int numRead = 0;
+ while (numRead < target)
+ {
+ int clusterOffset = (int)(_position % _reader.ClusterSize);
+ int toCopy = Math.Min(_reader.ClusterSize - clusterOffset, target - numRead);
+ Array.Copy(_clusterBuffer, clusterOffset, buffer, offset + numRead, toCopy);
+
+ // Remember how many we've read in total
+ numRead += toCopy;
+
+ // Increment the position
+ _position += toCopy;
+
+ // Abort if we've hit the end of the file
+ if (!TryLoadCurrentCluster())
+ {
+ break;
+ }
+ }
+
+ if (numRead == 0)
+ {
+ _atEOF = true;
+ }
+
+ return numRead;
+ }
+
+ public override long Seek(long offset, SeekOrigin origin)
+ {
+ long newPos = offset;
+ if (origin == SeekOrigin.Current)
+ {
+ newPos += _position;
+ }
+ else if (origin == SeekOrigin.End)
+ {
+ newPos += Length;
+ }
+
+ _position = newPos;
+ _atEOF = false;
+ return newPos;
+ }
+
+ public override void SetLength(long value)
+ {
+ long desiredNumClusters = (value + _reader.ClusterSize - 1) / _reader.ClusterSize;
+ long actualNumClusters = (_length + _reader.ClusterSize - 1) / _reader.ClusterSize;
+
+ if (desiredNumClusters < actualNumClusters)
+ {
+ uint cluster;
+ if (!TryGetClusterByPosition(value, out cluster))
+ {
+ throw new IOException("Internal state corrupt - unable to find cluster");
+ }
+
+ uint firstToFree = _fat.GetNext(cluster);
+ _fat.SetEndOfChain(cluster);
+ _fat.FreeChain(firstToFree);
+
+ while (_knownClusters.Count > desiredNumClusters)
+ {
+ _knownClusters.RemoveAt(_knownClusters.Count - 1);
+ }
+
+ _knownClusters.Add(FatBuffer.EndOfChain);
+
+ if (desiredNumClusters == 0)
+ {
+ FireFirstClusterAllocated(0);
+ }
+ }
+ else if (desiredNumClusters > actualNumClusters)
+ {
+ uint cluster;
+ while (!TryGetClusterByPosition(value, out cluster))
+ {
+ cluster = ExtendChain();
+ _reader.WipeCluster(cluster);
+ }
+ }
+
+ if (_length != value)
+ {
+ _length = (uint)value;
+ if (_position > _length)
+ {
+ _position = _length;
+ }
+ }
+ }
+
+ public override void Write(byte[] buffer, int offset, int count)
+ {
+ int bytesRemaining = count;
+
+ if (!CanWrite)
+ {
+ throw new IOException("Attempting to write to file not opened for writing");
+ }
+
+ if (count < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(count), count,
+ "Attempting to write negative number of bytes");
+ }
+
+ if (offset > buffer.Length || offset + count > buffer.Length)
+ {
+ throw new ArgumentException("Attempt to write bytes outside of the buffer");
+ }
+
+ // TODO: Free space check...
+ try
+ {
+ while (bytesRemaining > 0)
+ {
+ // Extend the stream until it encompasses _position
+ uint cluster;
+ while (!TryGetClusterByPosition(_position, out cluster))
+ {
+ cluster = ExtendChain();
+ _reader.WipeCluster(cluster);
+ }
+
+ // Fill this cluster with as much data as we can (WriteToCluster preserves existing cluster
+ // data, if necessary)
+ int numWritten = WriteToCluster(cluster, (int)(_position % _reader.ClusterSize), buffer, offset,
+ bytesRemaining);
+ offset += numWritten;
+ bytesRemaining -= numWritten;
+ _position += numWritten;
+ }
+
+ _length = (uint)Math.Max(_length, _position);
+ }
+ finally
+ {
+ _fat.Flush();
+ }
+
+ _atEOF = false;
+ }
+
+ ///
+ /// Writes up to the next cluster boundary, making sure to preserve existing data in the cluster
+ /// that falls outside of the updated range.
+ ///
+ /// The cluster to write to.
+ /// The file position of the write (within the cluster).
+ /// The buffer with the new data.
+ /// Offset into buffer of the first byte to write.
+ /// The maximum number of bytes to write.
+ /// The number of bytes written - either count, or the number that fit up to
+ /// the cluster boundary.
+ private int WriteToCluster(uint cluster, int pos, byte[] buffer, int offset, int count)
+ {
+ if (pos == 0 && count >= _reader.ClusterSize)
+ {
+ _currentCluster = cluster;
+ Array.Copy(buffer, offset, _clusterBuffer, 0, _reader.ClusterSize);
+
+ WriteCurrentCluster();
+
+ return _reader.ClusterSize;
+ }
+
+ // Partial cluster, so need to read existing cluster data first
+ LoadCluster(cluster);
+
+ int copyLength = Math.Min(count, _reader.ClusterSize - pos % _reader.ClusterSize);
+ Array.Copy(buffer, offset, _clusterBuffer, pos, copyLength);
+
+ WriteCurrentCluster();
+
+ return copyLength;
+ }
+
+ ///
+ /// Adds a new cluster to the end of the existing chain, by allocating a free cluster.
+ ///
+ /// The cluster allocated.
+ /// This method does not initialize the data in the cluster, the caller should
+ /// perform a write to ensure the cluster data is in known state.
+ private uint ExtendChain()
+ {
+ // Sanity check - make sure the final known cluster is the EOC marker
+ if (!_fat.IsEndOfChain(_knownClusters[_knownClusters.Count - 1]))
+ {
+ throw new IOException("Corrupt file system: final cluster isn't End-of-Chain");
+ }
+
+ uint cluster;
+ if (!_fat.TryGetFreeCluster(out cluster))
+ {
+ throw new IOException("Out of disk space");
+ }
+
+ _fat.SetEndOfChain(cluster);
+ if (_knownClusters.Count == 1)
+ {
+ FireFirstClusterAllocated(cluster);
+ }
+ else
+ {
+ _fat.SetNext(_knownClusters[_knownClusters.Count - 2], cluster);
+ }
+
+ _knownClusters[_knownClusters.Count - 1] = cluster;
+ _knownClusters.Add(_fat.GetNext(cluster));
+
+ return cluster;
+ }
+
+ private void FireFirstClusterAllocated(uint cluster)
+ {
+ if (FirstClusterChanged != null)
+ {
+ FirstClusterChanged(cluster);
+ }
+ }
+
+ private bool TryLoadCurrentCluster()
+ {
+ return TryLoadClusterByPosition(_position);
+ }
+
+ private bool TryLoadClusterByPosition(long pos)
+ {
+ uint cluster;
+ if (!TryGetClusterByPosition(pos, out cluster))
+ {
+ return false;
+ }
+
+ // Read the cluster, it's different to the one currently loaded
+ if (cluster != _currentCluster)
+ {
+ _reader.ReadCluster(cluster, _clusterBuffer, 0);
+ _currentCluster = cluster;
+ }
+
+ return true;
+ }
+
+ private void LoadCluster(uint cluster)
+ {
+ // Read the cluster, it's different to the one currently loaded
+ if (cluster != _currentCluster)
+ {
+ _reader.ReadCluster(cluster, _clusterBuffer, 0);
+ _currentCluster = cluster;
+ }
+ }
+
+ private void WriteCurrentCluster()
+ {
+ _reader.WriteCluster(_currentCluster, _clusterBuffer, 0);
+ }
+
+ private bool TryGetClusterByPosition(long pos, out uint cluster)
+ {
+ int index = (int)(pos / _reader.ClusterSize);
+
+ if (_knownClusters.Count <= index)
+ {
+ if (!TryPopulateKnownClusters(index))
+ {
+ cluster = uint.MaxValue;
+ return false;
+ }
+ }
+
+ // Chain is shorter than the current stream position
+ if (_knownClusters.Count <= index)
+ {
+ cluster = uint.MaxValue;
+ return false;
+ }
+
+ cluster = _knownClusters[index];
+
+ // This is the 'special' End-of-chain cluster identifer, so the stream position
+ // is greater than the actual file length.
+ if (_fat.IsEndOfChain(cluster))
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ private bool TryPopulateKnownClusters(int index)
+ {
+ uint lastKnown = _knownClusters[_knownClusters.Count - 1];
+ while (!_fat.IsEndOfChain(lastKnown) && _knownClusters.Count <= index)
+ {
+ lastKnown = _fat.GetNext(lastKnown);
+ _knownClusters.Add(lastKnown);
+ }
+
+ return _knownClusters.Count > index;
+ }
+
+ private uint DetectLength()
+ {
+ while (!_fat.IsEndOfChain(_knownClusters[_knownClusters.Count - 1]))
+ {
+ if (!TryPopulateKnownClusters(_knownClusters.Count))
+ {
+ throw new IOException("Corrupt file stream - unable to discover end of cluster chain");
+ }
+ }
+
+ return (uint)((_knownClusters.Count - 1) * (long)_reader.ClusterSize);
+ }
+ }
+}
\ No newline at end of file
diff --git a/DiscUtils/DiscUtils.Fat/Directory.cs b/DiscUtils/DiscUtils.Fat/Directory.cs
new file mode 100644
index 0000000..b0869d0
--- /dev/null
+++ b/DiscUtils/DiscUtils.Fat/Directory.cs
@@ -0,0 +1,569 @@
+//
+// Copyright (c) 2008-2011, Kenneth Bell
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the "Software"),
+// to deal in the Software without restriction, including without limitation
+// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+// and/or sell copies of the Software, and to permit persons to whom the
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+//
+
+namespace DiscUtils.Fat
+{
+ using DiscUtils.Streams;
+ using System;
+ using System.Collections.Generic;
+ using System.IO;
+
+ internal class Directory : IDisposable
+ {
+ private FatFileSystem _fileSystem;
+ private Directory _parent;
+ private long _parentId;
+ private Stream _dirStream;
+
+ private Dictionary _entries;
+ private List _freeEntries;
+ private long _endOfEntries;
+
+ private DirectoryEntry _selfEntry;
+ private long _selfEntryLocation;
+ private DirectoryEntry _parentEntry;
+ private long _parentEntryLocation;
+
+ internal Dictionary LongFileNames_ShortKey = new Dictionary(StringComparer.OrdinalIgnoreCase);
+ internal Dictionary LongFileNames_LongKey = new Dictionary(StringComparer.OrdinalIgnoreCase);
+
+ ///
+ /// Initializes a new instance of the Directory class. Use this constructor to represent non-root directories.
+ ///
+ /// The parent directory.
+ /// The identity of the entry representing this directory in the parent.
+ internal Directory(Directory parent, long parentId)
+ {
+ _fileSystem = parent._fileSystem;
+ _parent = parent;
+ _parentId = parentId;
+
+ DirectoryEntry dirEntry = ParentsChildEntry;
+ _dirStream = new ClusterStream(_fileSystem, FileAccess.ReadWrite, dirEntry.FirstCluster, uint.MaxValue);
+
+ LoadEntries();
+ }
+
+ ///
+ /// Initializes a new instance of the Directory class. Use this constructor to represent the root directory.
+ ///
+ /// The file system.
+ /// The stream containing the directory info.
+ internal Directory(FatFileSystem fileSystem, Stream dirStream)
+ {
+ _fileSystem = fileSystem;
+ _dirStream = dirStream;
+
+ LoadEntries();
+ }
+
+ private static string GetLfnChunk(byte[] buffer)
+ {
+ // see http://home.teleport.com/~brainy/lfn.htm
+ // NOTE: we assume ordinals are ok here.
+ char[] chars = new char[13];
+ chars[0] = (char)(256 * buffer[2] + buffer[1]);
+ chars[1] = (char)(256 * buffer[4] + buffer[3]);
+ chars[2] = (char)(256 * buffer[6] + buffer[5]);
+ chars[3] = (char)(256 * buffer[8] + buffer[7]);
+ chars[4] = (char)(256 * buffer[10] + buffer[9]);
+
+ chars[5] = (char)(256 * buffer[15] + buffer[14]);
+ chars[6] = (char)(256 * buffer[17] + buffer[16]);
+ chars[7] = (char)(256 * buffer[19] + buffer[18]);
+ chars[8] = (char)(256 * buffer[21] + buffer[20]);
+ chars[9] = (char)(256 * buffer[23] + buffer[22]);
+ chars[10] = (char)(256 * buffer[25] + buffer[24]);
+
+ chars[11] = (char)(256 * buffer[29] + buffer[28]);
+ chars[12] = (char)(256 * buffer[31] + buffer[30]);
+ string chunk = new string(chars);
+ int zero = chunk.IndexOf('\0');
+ return zero >= 0 ? chunk.Substring(0, zero) : chunk;
+ }
+
+ public FatFileSystem FileSystem
+ {
+ get { return _fileSystem; }
+ }
+
+ public bool IsEmpty
+ {
+ get { return _entries.Count == 0; }
+ }
+
+ public DirectoryEntry[] Entries
+ {
+ get { return new List(_entries.Values).ToArray(); }
+ }
+
+ #region Convenient accessors for special entries
+ internal DirectoryEntry ParentsChildEntry
+ {
+ get
+ {
+ if (_parent == null)
+ {
+ return new DirectoryEntry(_fileSystem.FatOptions, FileName.ParentEntryName, FatAttributes.Directory, _fileSystem.FatVariant);
+ }
+ else
+ {
+ return _parent.GetEntry(_parentId);
+ }
+ }
+
+ set
+ {
+ if (_parent != null)
+ {
+ _parent.UpdateEntry(_parentId, value);
+ }
+ }
+ }
+
+ internal DirectoryEntry SelfEntry
+ {
+ get
+ {
+ if (_parent == null)
+ {
+ // If we're the root directory, simulate the parent entry with a dummy record
+ return new DirectoryEntry(_fileSystem.FatOptions, FileName.Null, FatAttributes.Directory, _fileSystem.FatVariant);
+ }
+ else
+ {
+ return _selfEntry;
+ }
+ }
+
+ set
+ {
+ if (_selfEntryLocation >= 0)
+ {
+ _dirStream.Position = _selfEntryLocation;
+ value.WriteTo(_dirStream);
+ _selfEntry = value;
+ }
+ }
+ }
+
+ internal DirectoryEntry ParentEntry
+ {
+ get
+ {
+ return _parentEntry;
+ }
+
+ set
+ {
+ if (_parentEntryLocation < 0)
+ {
+ throw new IOException("No parent entry on disk to update");
+ }
+
+ _dirStream.Position = _parentEntryLocation;
+ value.WriteTo(_dirStream);
+ _parentEntry = value;
+ }
+ }
+ #endregion
+
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ public DirectoryEntry[] GetDirectories()
+ {
+ List dirs = new List(_entries.Count);
+ foreach (DirectoryEntry dirEntry in _entries.Values)
+ {
+ if ((dirEntry.Attributes & FatAttributes.Directory) != 0)
+ {
+ dirs.Add(dirEntry);
+ }
+ }
+
+ return dirs.ToArray();
+ }
+
+ public DirectoryEntry[] GetFiles()
+ {
+ List files = new List(_entries.Count);
+ foreach (DirectoryEntry dirEntry in _entries.Values)
+ {
+ if ((dirEntry.Attributes & (FatAttributes.Directory | FatAttributes.VolumeId)) == 0)
+ {
+ files.Add(dirEntry);
+ }
+ }
+
+ return files.ToArray();
+ }
+
+ public DirectoryEntry GetEntry(long id)
+ {
+ return (id < 0) ? null : _entries[id];
+ }
+
+ public Directory GetChildDirectory(FileName name)
+ {
+ long id = FindEntry(name);
+ if (id < 0)
+ {
+ return null;
+ }
+ else if ((_entries[id].Attributes & FatAttributes.Directory) == 0)
+ {
+ return null;
+ }
+ else
+ {
+ return _fileSystem.GetDirectory(this, id);
+ }
+ }
+
+ internal Directory CreateChildDirectory(FileName name)
+ {
+ long id = FindEntry(name);
+ if (id >= 0)
+ {
+ if ((_entries[id].Attributes & FatAttributes.Directory) == 0)
+ {
+ throw new IOException("A file exists with the same name");
+ }
+ else
+ {
+ return _fileSystem.GetDirectory(this, id);
+ }
+ }
+ else
+ {
+ try
+ {
+ uint firstCluster;
+ if (!_fileSystem.Fat.TryGetFreeCluster(out firstCluster))
+ {
+ throw new IOException("Failed to allocate first cluster for new directory");
+ }
+
+ _fileSystem.Fat.SetEndOfChain(firstCluster);
+
+ DirectoryEntry newEntry = new DirectoryEntry(_fileSystem.FatOptions, name, FatAttributes.Directory, _fileSystem.FatVariant);
+ newEntry.FirstCluster = firstCluster;
+ newEntry.CreationTime = _fileSystem.ConvertFromUtc(DateTime.UtcNow);
+ newEntry.LastWriteTime = newEntry.CreationTime;
+
+ id = AddEntry(newEntry);
+
+ PopulateNewChildDirectory(newEntry);
+
+ // Rather than just creating a new instance, pull it through the fileSystem cache
+ // to ensure the cache model is preserved.
+ return _fileSystem.GetDirectory(this, id);
+ }
+ finally
+ {
+ _fileSystem.Fat.Flush();
+ }
+ }
+ }
+
+ internal void AttachChildDirectory(FileName name, Directory newChild)
+ {
+ long id = FindEntry(name);
+ if (id >= 0)
+ {
+ throw new IOException("Directory entry already exists");
+ }
+
+ DirectoryEntry newEntry = new DirectoryEntry(newChild.ParentsChildEntry);
+ newEntry.Name = name;
+ AddEntry(newEntry);
+
+ DirectoryEntry newParentEntry = new DirectoryEntry(SelfEntry);
+ newParentEntry.Name = FileName.ParentEntryName;
+ newChild.ParentEntry = newParentEntry;
+ }
+
+ internal long FindVolumeId()
+ {
+ foreach (long id in _entries.Keys)
+ {
+ DirectoryEntry focus = _entries[id];
+ if ((focus.Attributes & FatAttributes.VolumeId) != 0)
+ {
+ return id;
+ }
+ }
+
+ return -1;
+ }
+
+ internal long FindEntry(FileName name)
+ {
+ foreach (long id in _entries.Keys)
+ {
+ DirectoryEntry focus = _entries[id];
+ if (focus.Name == name && (focus.Attributes & FatAttributes.VolumeId) == 0)
+ {
+ return id;
+ }
+ }
+
+ return -1;
+ }
+
+ internal SparseStream OpenFile(FileName name, FileMode mode, FileAccess fileAccess)
+ {
+ if (mode == FileMode.Append || mode == FileMode.Truncate)
+ {
+ throw new NotImplementedException();
+ }
+
+ long fileId = FindEntry(name);
+ bool exists = fileId != -1;
+
+ if (mode == FileMode.CreateNew && exists)
+ {
+ throw new IOException("File already exists");
+ }
+ else if (mode == FileMode.Open && !exists)
+ {
+ throw new FileNotFoundException("File not found", name.GetDisplayName(_fileSystem.FatOptions.FileNameEncoding));
+ }
+ else if ((mode == FileMode.Open || mode == FileMode.OpenOrCreate || mode == FileMode.Create) && exists)
+ {
+ SparseStream stream = new FatFileStream(_fileSystem, this, fileId, fileAccess);
+ if (mode == FileMode.Create)
+ {
+ stream.SetLength(0);
+ }
+
+ HandleAccessed(false);
+
+ return stream;
+ }
+ else if ((mode == FileMode.OpenOrCreate || mode == FileMode.CreateNew || mode == FileMode.Create) && !exists)
+ {
+ // Create new file
+ DirectoryEntry newEntry = new DirectoryEntry(_fileSystem.FatOptions, name, FatAttributes.Archive, _fileSystem.FatVariant);
+ newEntry.FirstCluster = 0; // i.e. Zero-length
+ newEntry.CreationTime = _fileSystem.ConvertFromUtc(DateTime.UtcNow);
+ newEntry.LastWriteTime = newEntry.CreationTime;
+
+ fileId = AddEntry(newEntry);
+
+ return new FatFileStream(_fileSystem, this, fileId, fileAccess);
+ }
+ else
+ {
+ // Should never get here...
+ throw new NotImplementedException();
+ }
+ }
+
+ internal long AddEntry(DirectoryEntry newEntry)
+ {
+ // Unlink an entry from the free list (or add to the end of the existing directory)
+ long pos;
+ if (_freeEntries.Count > 0)
+ {
+ pos = _freeEntries[0];
+ _freeEntries.RemoveAt(0);
+ }
+ else
+ {
+ pos = _endOfEntries;
+ _endOfEntries += 32;
+ }
+
+ // Put the new entry into it's slot
+ _dirStream.Position = pos;
+ newEntry.WriteTo(_dirStream);
+
+ // Update internal structures to reflect new entry (as if read from disk)
+ _entries.Add(pos, newEntry);
+
+ HandleAccessed(true);
+
+ return pos;
+ }
+
+ internal void DeleteEntry(long id, bool releaseContents)
+ {
+ if (id < 0)
+ {
+ throw new IOException("Attempt to delete unknown directory entry");
+ }
+
+ try
+ {
+ DirectoryEntry entry = _entries[id];
+
+ DirectoryEntry copy = new DirectoryEntry(entry);
+ copy.Name = entry.Name.Deleted();
+ _dirStream.Position = id;
+ copy.WriteTo(_dirStream);
+
+ if (releaseContents)
+ {
+ _fileSystem.Fat.FreeChain(entry.FirstCluster);
+ }
+
+ _entries.Remove(id);
+ _freeEntries.Add(id);
+
+ HandleAccessed(true);
+ }
+ finally
+ {
+ _fileSystem.Fat.Flush();
+ }
+ }
+
+ internal void UpdateEntry(long id, DirectoryEntry entry)
+ {
+ if (id < 0)
+ {
+ throw new IOException("Attempt to update unknown directory entry");
+ }
+
+ _dirStream.Position = id;
+ entry.WriteTo(_dirStream);
+ _entries[id] = entry;
+ }
+
+ private void LoadEntries()
+ {
+ _entries = new Dictionary();
+ _freeEntries = new List();
+
+ _selfEntryLocation = -1;
+ _parentEntryLocation = -1;
+
+ string lfn = null; //+++
+ while (_dirStream.Position < _dirStream.Length)
+ {
+ long streamPos = _dirStream.Position;
+ DirectoryEntry entry = new DirectoryEntry(_fileSystem.FatOptions, _dirStream, _fileSystem.FatVariant);
+
+ if (entry.Attributes == (FatAttributes.ReadOnly | FatAttributes.Hidden | FatAttributes.System | FatAttributes.VolumeId))
+ {
+ // Long File Name entry
+ _dirStream.Position = streamPos; //+++
+ lfn = GetLfnChunk(StreamUtilities.ReadExact(_dirStream, 32)) + lfn; //+++
+ }
+ else if (entry.Name.IsDeleted())
+ {
+ // E5 = Free Entry
+ _freeEntries.Add(streamPos);
+ lfn = null; //+++
+ }
+ else if (entry.Name == FileName.SelfEntryName)
+ {
+ _selfEntry = entry;
+ _selfEntryLocation = streamPos;
+ lfn = null; //+++
+ }
+ else if (entry.Name == FileName.ParentEntryName)
+ {
+ _parentEntry = entry;
+ _parentEntryLocation = streamPos;
+ lfn = null; //+++
+ }
+ else if (entry.Name.IsEndMarker())
+ {
+ // Free Entry, no more entries available
+ _endOfEntries = streamPos;
+ break;
+ }
+ else
+ {
+ if (lfn != null) //+++
+ { //+++
+ LongFileNames_ShortKey.Add(entry.Name.GetDisplayName(_fileSystem.FatOptions.FileNameEncoding), lfn); //+++
+ LongFileNames_LongKey.Add(lfn, entry.Name.GetDisplayName(_fileSystem.FatOptions.FileNameEncoding)); //+++
+ } //+++
+ _entries.Add(streamPos, entry);
+ lfn = null; //+++
+ }
+ }
+ }
+
+ private void HandleAccessed(bool forWrite)
+ {
+ if (_fileSystem.CanWrite && _parent != null)
+ {
+ DateTime now = DateTime.Now;
+ DirectoryEntry entry = SelfEntry;
+
+ DateTime oldAccessTime = entry.LastAccessTime;
+ DateTime oldWriteTime = entry.LastWriteTime;
+
+ entry.LastAccessTime = now;
+ if (forWrite)
+ {
+ entry.LastWriteTime = now;
+ }
+
+ if (entry.LastAccessTime != oldAccessTime || entry.LastWriteTime != oldWriteTime)
+ {
+ SelfEntry = entry;
+
+ DirectoryEntry parentEntry = ParentsChildEntry;
+ parentEntry.LastAccessTime = entry.LastAccessTime;
+ parentEntry.LastWriteTime = entry.LastWriteTime;
+ ParentsChildEntry = parentEntry;
+ }
+ }
+ }
+
+ private void PopulateNewChildDirectory(DirectoryEntry newEntry)
+ {
+ // Populate new directory with initial (special) entries. First one is easy, just change the name!
+ using (ClusterStream stream = new ClusterStream(_fileSystem, FileAccess.Write, newEntry.FirstCluster, uint.MaxValue))
+ {
+ // First is the self-referencing entry...
+ DirectoryEntry selfEntry = new DirectoryEntry(newEntry);
+ selfEntry.Name = FileName.SelfEntryName;
+ selfEntry.WriteTo(stream);
+
+ // Second is a clone of our self entry (i.e. parent) - though dates are odd...
+ DirectoryEntry parentEntry = new DirectoryEntry(SelfEntry);
+ parentEntry.Name = FileName.ParentEntryName;
+ parentEntry.CreationTime = newEntry.CreationTime;
+ parentEntry.LastWriteTime = newEntry.LastWriteTime;
+ parentEntry.WriteTo(stream);
+ }
+ }
+
+ private void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ _dirStream.Dispose();
+ }
+ }
+ }
+}
diff --git a/DiscUtils/DiscUtils.Fat/DirectoryEntry.cs b/DiscUtils/DiscUtils.Fat/DirectoryEntry.cs
new file mode 100644
index 0000000..7c9a618
--- /dev/null
+++ b/DiscUtils/DiscUtils.Fat/DirectoryEntry.cs
@@ -0,0 +1,210 @@
+//
+// Copyright (c) 2008-2011, Kenneth Bell
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the "Software"),
+// to deal in the Software without restriction, including without limitation
+// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+// and/or sell copies of the Software, and to permit persons to whom the
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.IO;
+using DiscUtils.Streams;
+
+namespace DiscUtils.Fat
+{
+ internal class DirectoryEntry
+ {
+ private readonly FatType _fatVariant;
+ private readonly FatFileSystemOptions _options;
+ private byte _attr;
+ private ushort _creationDate;
+ private ushort _creationTime;
+ private byte _creationTimeTenth;
+ private uint _fileSize;
+ private ushort _firstClusterHi;
+ private ushort _firstClusterLo;
+ private ushort _lastAccessDate;
+ private ushort _lastWriteDate;
+ private ushort _lastWriteTime;
+
+ internal DirectoryEntry(FatFileSystemOptions options, Stream stream, FatType fatVariant)
+ {
+ _options = options;
+ _fatVariant = fatVariant;
+ byte[] buffer = StreamUtilities.ReadExact(stream, 32);
+ Load(buffer, 0);
+ }
+
+ internal DirectoryEntry(FatFileSystemOptions options, FileName name, FatAttributes attrs, FatType fatVariant)
+ {
+ _options = options;
+ _fatVariant = fatVariant;
+ Name = name;
+ _attr = (byte)attrs;
+ }
+
+ internal DirectoryEntry(DirectoryEntry toCopy)
+ {
+ _options = toCopy._options;
+ _fatVariant = toCopy._fatVariant;
+ Name = toCopy.Name;
+ _attr = toCopy._attr;
+ _creationTimeTenth = toCopy._creationTimeTenth;
+ _creationTime = toCopy._creationTime;
+ _creationDate = toCopy._creationDate;
+ _lastAccessDate = toCopy._lastAccessDate;
+ _firstClusterHi = toCopy._firstClusterHi;
+ _lastWriteTime = toCopy._lastWriteTime;
+ _firstClusterLo = toCopy._firstClusterLo;
+ _fileSize = toCopy._fileSize;
+ }
+
+ public FatAttributes Attributes
+ {
+ get { return (FatAttributes)_attr; }
+ set { _attr = (byte)value; }
+ }
+
+ public DateTime CreationTime
+ {
+ get { return FileTimeToDateTime(_creationDate, _creationTime, _creationTimeTenth); }
+ set { DateTimeToFileTime(value, out _creationDate, out _creationTime, out _creationTimeTenth); }
+ }
+
+ public int FileSize
+ {
+ get { return (int)_fileSize; }
+ set { _fileSize = (uint)value; }
+ }
+
+ public uint FirstCluster
+ {
+ get
+ {
+ if (_fatVariant == FatType.Fat32)
+ {
+ return (uint)(_firstClusterHi << 16) | _firstClusterLo;
+ }
+ return _firstClusterLo;
+ }
+
+ set
+ {
+ if (_fatVariant == FatType.Fat32)
+ {
+ _firstClusterHi = (ushort)((value >> 16) & 0xFFFF);
+ }
+
+ _firstClusterLo = (ushort)(value & 0xFFFF);
+ }
+ }
+
+ public DateTime LastAccessTime
+ {
+ get { return FileTimeToDateTime(_lastAccessDate, 0, 0); }
+ set { DateTimeToFileTime(value, out _lastAccessDate); }
+ }
+
+ public DateTime LastWriteTime
+ {
+ get { return FileTimeToDateTime(_lastWriteDate, _lastWriteTime, 0); }
+ set { DateTimeToFileTime(value, out _lastWriteDate, out _lastWriteTime); }
+ }
+
+ public FileName Name { get; set; }
+
+ internal void WriteTo(Stream stream)
+ {
+ byte[] buffer = new byte[32];
+
+ Name.GetBytes(buffer, 0);
+ buffer[11] = _attr;
+ buffer[13] = _creationTimeTenth;
+ EndianUtilities.WriteBytesLittleEndian(_creationTime, buffer, 14);
+ EndianUtilities.WriteBytesLittleEndian(_creationDate, buffer, 16);
+ EndianUtilities.WriteBytesLittleEndian(_lastAccessDate, buffer, 18);
+ EndianUtilities.WriteBytesLittleEndian(_firstClusterHi, buffer, 20);
+ EndianUtilities.WriteBytesLittleEndian(_lastWriteTime, buffer, 22);
+ EndianUtilities.WriteBytesLittleEndian(_lastWriteDate, buffer, 24);
+ EndianUtilities.WriteBytesLittleEndian(_firstClusterLo, buffer, 26);
+ EndianUtilities.WriteBytesLittleEndian(_fileSize, buffer, 28);
+
+ stream.Write(buffer, 0, buffer.Length);
+ }
+
+ private static DateTime FileTimeToDateTime(ushort date, ushort time, byte tenths)
+ {
+ if (date == 0 || date == 0xFFFF)
+ {
+ // Return Epoch - this is an invalid date
+ return FatFileSystem.Epoch;
+ }
+
+ int year = 1980 + ((date & 0xFE00) >> 9);
+ int month = (date & 0x01E0) >> 5;
+ int day = date & 0x001F;
+ int hour = (time & 0xF800) >> 11;
+ int minute = (time & 0x07E0) >> 5;
+ int second = (time & 0x001F) * 2 + tenths / 100;
+ int millis = tenths % 100 * 10;
+
+ return new DateTime(year, month, day, hour, minute, second, millis);
+ }
+
+ private static void DateTimeToFileTime(DateTime value, out ushort date)
+ {
+ byte tenths;
+ ushort time;
+ DateTimeToFileTime(value, out date, out time, out tenths);
+ }
+
+ private static void DateTimeToFileTime(DateTime value, out ushort date, out ushort time)
+ {
+ byte tenths;
+ DateTimeToFileTime(value, out date, out time, out tenths);
+ }
+
+ private static void DateTimeToFileTime(DateTime value, out ushort date, out ushort time, out byte tenths)
+ {
+ if (value.Year < 1980)
+ {
+ value = FatFileSystem.Epoch;
+ }
+
+ date =
+ (ushort)((((value.Year - 1980) << 9) & 0xFE00) | ((value.Month << 5) & 0x01E0) | (value.Day & 0x001F));
+ time =
+ (ushort)(((value.Hour << 11) & 0xF800) | ((value.Minute << 5) & 0x07E0) | ((value.Second / 2) & 0x001F));
+ tenths = (byte)(value.Second % 2 * 100 + value.Millisecond / 10);
+ }
+
+ private void Load(byte[] data, int offset)
+ {
+ Name = new FileName(data, offset);
+ _attr = data[offset + 11];
+ _creationTimeTenth = data[offset + 13];
+ _creationTime = EndianUtilities.ToUInt16LittleEndian(data, offset + 14);
+ _creationDate = EndianUtilities.ToUInt16LittleEndian(data, offset + 16);
+ _lastAccessDate = EndianUtilities.ToUInt16LittleEndian(data, offset + 18);
+ _firstClusterHi = EndianUtilities.ToUInt16LittleEndian(data, offset + 20);
+ _lastWriteTime = EndianUtilities.ToUInt16LittleEndian(data, offset + 22);
+ _lastWriteDate = EndianUtilities.ToUInt16LittleEndian(data, offset + 24);
+ _firstClusterLo = EndianUtilities.ToUInt16LittleEndian(data, offset + 26);
+ _fileSize = EndianUtilities.ToUInt32LittleEndian(data, offset + 28);
+ }
+ }
+}
\ No newline at end of file
diff --git a/DiscUtils/DiscUtils.Fat/FatAttributes.cs b/DiscUtils/DiscUtils.Fat/FatAttributes.cs
new file mode 100644
index 0000000..1651b40
--- /dev/null
+++ b/DiscUtils/DiscUtils.Fat/FatAttributes.cs
@@ -0,0 +1,37 @@
+//
+// Copyright (c) 2008-2011, Kenneth Bell
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the "Software"),
+// to deal in the Software without restriction, including without limitation
+// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+// and/or sell copies of the Software, and to permit persons to whom the
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+
+namespace DiscUtils.Fat
+{
+ [Flags]
+ internal enum FatAttributes : byte
+ {
+ ReadOnly = 0x01,
+ Hidden = 0x02,
+ System = 0x04,
+ VolumeId = 0x08,
+ Directory = 0x10,
+ Archive = 0x20
+ }
+}
\ No newline at end of file
diff --git a/DiscUtils/DiscUtils.Fat/FatBuffer.cs b/DiscUtils/DiscUtils.Fat/FatBuffer.cs
new file mode 100644
index 0000000..63dbddb
--- /dev/null
+++ b/DiscUtils/DiscUtils.Fat/FatBuffer.cs
@@ -0,0 +1,267 @@
+//
+// Copyright (c) 2008-2011, Kenneth Bell
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the "Software"),
+// to deal in the Software without restriction, including without limitation
+// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+// and/or sell copies of the Software, and to permit persons to whom the
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using DiscUtils.Streams;
+
+namespace DiscUtils.Fat
+{
+ internal class FatBuffer
+ {
+ ///
+ /// The End-of-chain marker to WRITE (SetNext). Don't use this value to test for end of chain.
+ ///
+ ///
+ /// The actual end-of-chain marker bits on disk vary by FAT type, and can end ...F8 through ...FF.
+ ///
+ public const uint EndOfChain = 0xFFFFFFFF;
+
+ ///
+ /// The Bad-Cluster marker to WRITE (SetNext). Don't use this value to test for bad clusters.
+ ///
+ ///
+ /// The actual bad-cluster marker bits on disk vary by FAT type.
+ ///
+ public const uint BadCluster = 0xFFFFFFF7;
+
+ ///
+ /// The Free-Cluster marker to WRITE (SetNext). Don't use this value to test for free clusters.
+ ///
+ ///
+ /// The actual free-cluster marker bits on disk vary by FAT type.
+ ///
+ public const uint FreeCluster = 0;
+
+ private const uint DirtyRegionSize = 512;
+ private readonly byte[] _buffer;
+ private readonly Dictionary _dirtySectors;
+
+ private readonly FatType _type;
+ private uint _nextFreeCandidate;
+
+ public FatBuffer(FatType type, byte[] buffer)
+ {
+ _type = type;
+ _buffer = buffer;
+ _dirtySectors = new Dictionary();
+ }
+
+ internal int NumEntries
+ {
+ get
+ {
+ switch (_type)
+ {
+ case FatType.Fat12:
+ return _buffer.Length / 3 * 2;
+ case FatType.Fat16:
+ return _buffer.Length / 2;
+ default: // FAT32
+ return _buffer.Length / 4;
+ }
+ }
+ }
+
+ internal int Size
+ {
+ get { return _buffer.Length; }
+ }
+
+ internal bool IsFree(uint val)
+ {
+ return val == 0;
+ }
+
+ internal bool IsEndOfChain(uint val)
+ {
+ switch (_type)
+ {
+ case FatType.Fat12:
+ return (val & 0x0FFF) >= 0x0FF8;
+ case FatType.Fat16:
+ return (val & 0xFFFF) >= 0xFFF8;
+ case FatType.Fat32:
+ return (val & 0x0FFFFFF8) >= 0x0FFFFFF8;
+ default:
+ throw new ArgumentException("Unknown FAT type");
+ }
+ }
+
+ internal bool IsBadCluster(uint val)
+ {
+ switch (_type)
+ {
+ case FatType.Fat12:
+ return (val & 0x0FFF) == 0x0FF7;
+ case FatType.Fat16:
+ return (val & 0xFFFF) == 0xFFF7;
+ case FatType.Fat32:
+ return (val & 0x0FFFFFF8) == 0x0FFFFFF7;
+ default:
+ throw new ArgumentException("Unknown FAT type");
+ }
+ }
+
+ internal uint GetNext(uint cluster)
+ {
+ if (_type == FatType.Fat16)
+ {
+ return EndianUtilities.ToUInt16LittleEndian(_buffer, (int)(cluster * 2));
+ }
+ if (_type == FatType.Fat32)
+ {
+ return EndianUtilities.ToUInt32LittleEndian(_buffer, (int)(cluster * 4)) & 0x0FFFFFFF;
+ }
+
+ // FAT12
+ if ((cluster & 1) != 0)
+ {
+ return
+ (uint)((EndianUtilities.ToUInt16LittleEndian(_buffer, (int)(cluster + cluster / 2)) >> 4) & 0x0FFF);
+ }
+ return (uint)(EndianUtilities.ToUInt16LittleEndian(_buffer, (int)(cluster + cluster / 2)) & 0x0FFF);
+ }
+
+ internal void SetEndOfChain(uint cluster)
+ {
+ SetNext(cluster, EndOfChain);
+ }
+
+ internal void SetBadCluster(uint cluster)
+ {
+ SetNext(cluster, BadCluster);
+ }
+
+ internal void SetFree(uint cluster)
+ {
+ if (cluster < _nextFreeCandidate)
+ {
+ _nextFreeCandidate = cluster;
+ }
+
+ SetNext(cluster, FreeCluster);
+ }
+
+ internal void SetNext(uint cluster, uint next)
+ {
+ if (_type == FatType.Fat16)
+ {
+ MarkDirty(cluster * 2);
+ EndianUtilities.WriteBytesLittleEndian((ushort)next, _buffer, (int)(cluster * 2));
+ }
+ else if (_type == FatType.Fat32)
+ {
+ MarkDirty(cluster * 4);
+ uint oldVal = EndianUtilities.ToUInt32LittleEndian(_buffer, (int)(cluster * 4));
+ uint newVal = (oldVal & 0xF0000000) | (next & 0x0FFFFFFF);
+ EndianUtilities.WriteBytesLittleEndian(newVal, _buffer, (int)(cluster * 4));
+ }
+ else
+ {
+ uint offset = cluster + cluster / 2;
+ MarkDirty(offset);
+ MarkDirty(offset + 1); // On alternate sector boundaries, cluster info crosses two sectors
+
+ ushort maskedOldVal;
+ if ((cluster & 1) != 0)
+ {
+ next = next << 4;
+ maskedOldVal = (ushort)(EndianUtilities.ToUInt16LittleEndian(_buffer, (int)offset) & 0x000F);
+ }
+ else
+ {
+ next = next & 0x0FFF;
+ maskedOldVal = (ushort)(EndianUtilities.ToUInt16LittleEndian(_buffer, (int)offset) & 0xF000);
+ }
+
+ ushort newVal = (ushort)(maskedOldVal | next);
+
+ EndianUtilities.WriteBytesLittleEndian(newVal, _buffer, (int)offset);
+ }
+ }
+
+ internal bool TryGetFreeCluster(out uint cluster)
+ {
+ // Simple scan - don't hold a free list...
+ uint numEntries = (uint)NumEntries;
+ for (uint i = 0; i < numEntries; i++)
+ {
+ uint candidate = (i + _nextFreeCandidate) % numEntries;
+ if (IsFree(GetNext(candidate)))
+ {
+ cluster = candidate;
+ _nextFreeCandidate = candidate + 1;
+ return true;
+ }
+ }
+
+ cluster = 0;
+ return false;
+ }
+
+ internal void FreeChain(uint head)
+ {
+ foreach (uint cluster in GetChain(head))
+ {
+ SetFree(cluster);
+ }
+ }
+
+ internal List GetChain(uint head)
+ {
+ List result = new List();
+
+ if (head != 0)
+ {
+ uint focus = head;
+ while (!IsEndOfChain(focus))
+ {
+ result.Add(focus);
+ focus = GetNext(focus);
+ }
+ }
+
+ return result;
+ }
+
+ internal void MarkDirty(uint offset)
+ {
+ _dirtySectors[offset / DirtyRegionSize] = offset / DirtyRegionSize;
+ }
+
+ internal void WriteDirtyRegions(Stream stream, long position)
+ {
+ foreach (uint val in _dirtySectors.Values)
+ {
+ stream.Position = position + val * DirtyRegionSize;
+ stream.Write(_buffer, (int)(val * DirtyRegionSize), (int)DirtyRegionSize);
+ }
+ }
+
+ internal void ClearDirtyRegions()
+ {
+ _dirtySectors.Clear();
+ }
+ }
+}
\ No newline at end of file
diff --git a/DiscUtils/DiscUtils.Fat/FatFileStream.cs b/DiscUtils/DiscUtils.Fat/FatFileStream.cs
new file mode 100644
index 0000000..6605928
--- /dev/null
+++ b/DiscUtils/DiscUtils.Fat/FatFileStream.cs
@@ -0,0 +1,141 @@
+//
+// Copyright (c) 2008-2011, Kenneth Bell
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the "Software"),
+// to deal in the Software without restriction, including without limitation
+// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+// and/or sell copies of the Software, and to permit persons to whom the
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+//
+
+namespace DiscUtils.Fat
+{
+ using DiscUtils.Streams;
+ using System;
+ using System.Collections.Generic;
+ using System.IO;
+
+ internal class FatFileStream : SparseStream
+ {
+ private Directory _dir;
+ private long _dirId;
+ private ClusterStream _stream;
+
+ private bool didWrite = false;
+
+ public FatFileStream(FatFileSystem fileSystem, Directory dir, long fileId, FileAccess access)
+ {
+ _dir = dir;
+ _dirId = fileId;
+
+ DirectoryEntry dirEntry = _dir.GetEntry(_dirId);
+ _stream = new ClusterStream(fileSystem, access, (uint)dirEntry.FirstCluster, (uint)dirEntry.FileSize);
+ _stream.FirstClusterChanged += FirstClusterAllocatedHandler;
+ }
+
+ public override long Position
+ {
+ get { return _stream.Position; }
+ set { _stream.Position = value; }
+ }
+
+ public override bool CanRead
+ {
+ get { return _stream.CanRead; }
+ }
+
+ public override bool CanSeek
+ {
+ get { return _stream.CanSeek; }
+ }
+
+ public override bool CanWrite
+ {
+ get { return _stream.CanWrite; }
+ }
+
+ public override long Length
+ {
+ get { return _stream.Length; }
+ }
+
+ public override IEnumerable Extents
+ {
+ get
+ {
+ return new StreamExtent[] { new StreamExtent(0, Length) };
+ }
+ }
+
+ public override void Close()
+ {
+ if (_dir.FileSystem.CanWrite)
+ {
+ try
+ {
+ DateTime now = _dir.FileSystem.ConvertFromUtc(DateTime.UtcNow);
+
+ DirectoryEntry dirEntry = _dir.GetEntry(_dirId);
+ dirEntry.LastAccessTime = now;
+ if (didWrite)
+ {
+ dirEntry.FileSize = (int)_stream.Length;
+ dirEntry.LastWriteTime = now;
+ }
+
+ _dir.UpdateEntry(_dirId, dirEntry);
+ }
+ finally
+ {
+ base.Close();
+ }
+ }
+ }
+
+ public override void SetLength(long value)
+ {
+ didWrite = true;
+ _stream.SetLength(value);
+ }
+
+ public override void Write(byte[] buffer, int offset, int count)
+ {
+ didWrite = true;
+ _stream.Write(buffer, offset, count);
+ }
+
+ public override void Flush()
+ {
+ _stream.Flush();
+ }
+
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+ return _stream.Read(buffer, offset, count);
+ }
+
+ public override long Seek(long offset, SeekOrigin origin)
+ {
+ return _stream.Seek(offset, origin);
+ }
+
+ private void FirstClusterAllocatedHandler(uint cluster)
+ {
+ DirectoryEntry dirEntry = _dir.GetEntry(_dirId);
+ dirEntry.FirstCluster = cluster;
+ _dir.UpdateEntry(_dirId, dirEntry);
+ }
+ }
+}
diff --git a/DiscUtils/DiscUtils.Fat/FatFileSystem.cs b/DiscUtils/DiscUtils.Fat/FatFileSystem.cs
new file mode 100644
index 0000000..31ec2db
--- /dev/null
+++ b/DiscUtils/DiscUtils.Fat/FatFileSystem.cs
@@ -0,0 +1,2048 @@
+//
+// Copyright (c) 2008-2011, Kenneth Bell
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the "Software"),
+// to deal in the Software without restriction, including without limitation
+// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+// and/or sell copies of the Software, and to permit persons to whom the
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+//
+
+namespace DiscUtils.Fat
+{
+ using DiscUtils.Internal;
+ using DiscUtils.Streams;
+ using System;
+ using System.Collections.Generic;
+ using System.Globalization;
+ using System.IO;
+ using System.Text;
+ using System.Text.RegularExpressions;
+
+ ///
+ /// Class for accessing FAT file systems.
+ ///
+ public sealed class FatFileSystem : DiscFileSystem
+ {
+ ///
+ /// The Epoch for FAT file systems (1st Jan, 1980).
+ ///
+ public static readonly DateTime Epoch = new DateTime(1980, 1, 1);
+
+ private TimeConverter _timeConverter;
+ private Stream _data;
+ private Ownership _ownsData;
+ private byte[] _bootSector;
+ private FileAllocationTable _fat;
+ private ClusterReader _clusterReader;
+ private Directory _rootDir;
+ private Dictionary _dirCache;
+
+ private FatType _type;
+ private string _bpbOEMName;
+ private ushort _bpbBytesPerSec;
+ private byte _bpbSecPerClus;
+ private ushort _bpbRsvdSecCnt;
+ private byte _bpbNumFATs;
+ private ushort _bpbRootEntCnt;
+ private ushort _bpbTotSec16;
+ private byte _bpbMedia;
+ private ushort _bpbFATSz16;
+ private ushort _bpbSecPerTrk;
+ private ushort _bpbNumHeads;
+ private uint _bpbHiddSec;
+ private uint _bpbTotSec32;
+
+ private byte _bsDrvNum;
+ private byte _bsBootSig;
+ private uint _bsVolId;
+ private string _bsVolLab;
+ private string _bsFilSysType;
+
+ private uint _bpbFATSz32;
+ private ushort _bpbExtFlags;
+ private ushort _bpbFSVer;
+ private uint _bpbRootClus;
+ private ushort _bpbFSInfo;
+ private ushort _bpbBkBootSec;
+
+ ///
+ /// Initializes a new instance of the FatFileSystem class.
+ ///
+ /// The stream containing the file system.
+ ///
+ /// Local time is the effective timezone of the new instance.
+ ///
+ public FatFileSystem(Stream data)
+ : base(new FatFileSystemOptions())
+ {
+ _dirCache = new Dictionary();
+ _timeConverter = DefaultTimeConverter;
+ Initialize(data);
+ }
+
+ ///
+ /// Initializes a new instance of the FatFileSystem class.
+ ///
+ /// The stream containing the file system.
+ /// Indicates if the new instance should take ownership
+ /// of .
+ ///
+ /// Local time is the effective timezone of the new instance.
+ ///
+ public FatFileSystem(Stream data, Ownership ownsData)
+ : base(new FatFileSystemOptions())
+ {
+ _dirCache = new Dictionary();
+ _timeConverter = DefaultTimeConverter;
+ Initialize(data);
+ _ownsData = ownsData;
+ }
+
+ ///
+ /// Initializes a new instance of the FatFileSystem class.
+ ///
+ /// The stream containing the file system.
+ /// A delegate to convert to/from the file system's timezone.
+ public FatFileSystem(Stream data, TimeConverter timeConverter)
+ : base(new FatFileSystemOptions())
+ {
+ _dirCache = new Dictionary();
+ _timeConverter = timeConverter;
+ Initialize(data);
+ }
+
+ ///
+ /// Initializes a new instance of the FatFileSystem class.
+ ///
+ /// The stream containing the file system.
+ /// Indicates if the new instance should take ownership
+ /// of .
+ /// A delegate to convert to/from the file system's timezone.
+ public FatFileSystem(Stream data, Ownership ownsData, TimeConverter timeConverter)
+ : base(new FatFileSystemOptions())
+ {
+ _dirCache = new Dictionary();
+ _timeConverter = timeConverter;
+ Initialize(data);
+ _ownsData = ownsData;
+ }
+
+ ///
+ /// Initializes a new instance of the FatFileSystem class.
+ ///
+ /// The stream containing the file system.
+ /// Indicates if the new instance should take ownership
+ /// of .
+ /// The parameters for the file system.
+ public FatFileSystem(Stream data, Ownership ownsData, FileSystemParameters parameters)
+ : base(new FatFileSystemOptions(parameters))
+ {
+ _dirCache = new Dictionary();
+
+ if (parameters != null && parameters.TimeConverter != null)
+ {
+ _timeConverter = parameters.TimeConverter;
+ }
+ else
+ {
+ _timeConverter = DefaultTimeConverter;
+ }
+
+ Initialize(data);
+ _ownsData = ownsData;
+ }
+
+ private delegate void EntryUpdateAction(DirectoryEntry entry);
+
+ ///
+ /// Gets the FAT file system options, which can be modified.
+ ///
+ public FatFileSystemOptions FatOptions
+ {
+ get { return (FatFileSystemOptions)Options; }
+ }
+
+ ///
+ /// Gets the FAT variant of the file system.
+ ///
+ public FatType FatVariant
+ {
+ get { return _type; }
+ }
+
+ ///
+ /// Gets the friendly name for the file system, including FAT variant.
+ ///
+ public override string FriendlyName
+ {
+ get
+ {
+ switch (_type)
+ {
+ case FatType.Fat12: return "Microsoft FAT12";
+ case FatType.Fat16: return "Microsoft FAT16";
+ case FatType.Fat32: return "Microsoft FAT32";
+ default: return "Unknown FAT";
+ }
+ }
+ }
+
+ ///
+ /// Gets the OEM name from the file system.
+ ///
+ public string OemName
+ {
+ get { return _bpbOEMName; }
+ }
+
+ ///
+ /// Gets the number of bytes per sector (as stored in the file-system meta data).
+ ///
+ public int BytesPerSector
+ {
+ get { return _bpbBytesPerSec; }
+ }
+
+ ///
+ /// Gets the number of contiguous sectors that make up one cluster.
+ ///
+ public byte SectorsPerCluster
+ {
+ get { return _bpbSecPerClus; }
+ }
+
+ ///
+ /// Gets the number of reserved sectors at the start of the disk.
+ ///
+ public int ReservedSectorCount
+ {
+ get { return _bpbRsvdSecCnt; }
+ }
+
+ ///
+ /// Gets the number of FATs present.
+ ///
+ public byte FatCount
+ {
+ get { return _bpbNumFATs; }
+ }
+
+ ///
+ /// Gets the maximum number of root directory entries (on FAT variants that have a limit).
+ ///
+ public int MaxRootDirectoryEntries
+ {
+ get { return _bpbRootEntCnt; }
+ }
+
+ ///
+ /// Gets the total number of sectors on the disk.
+ ///
+ public long TotalSectors
+ {
+ get { return (_bpbTotSec16 != 0) ? _bpbTotSec16 : _bpbTotSec32; }
+ }
+
+ ///
+ /// Gets the Media marker byte, which indicates fixed or removable media.
+ ///
+ public byte Media
+ {
+ get { return _bpbMedia; }
+ }
+
+ ///
+ /// Gets the size of a single FAT, in sectors.
+ ///
+ public long FatSize
+ {
+ get { return (_bpbFATSz16 != 0) ? _bpbFATSz16 : _bpbFATSz32; }
+ }
+
+ ///
+ /// Gets the number of sectors per logical track.
+ ///
+ public int SectorsPerTrack
+ {
+ get { return _bpbSecPerTrk; }
+ }
+
+ ///
+ /// Gets the number of logical heads.
+ ///
+ public int Heads
+ {
+ get { return _bpbNumHeads; }
+ }
+
+ ///
+ /// Gets the number of hidden sectors, hiding partition tables, etc.
+ ///
+ public long HiddenSectors
+ {
+ get { return _bpbHiddSec; }
+ }
+
+ ///
+ /// Gets the BIOS drive number for BIOS Int 13h calls.
+ ///
+ public byte BiosDriveNumber
+ {
+ get { return _bsDrvNum; }
+ }
+
+ ///
+ /// Gets a value indicating whether the VolumeId, VolumeLabel and FileSystemType fields are valid.
+ ///
+ public bool ExtendedBootSignaturePresent
+ {
+ get { return _bsBootSig == 0x29; }
+ }
+
+ ///
+ /// Gets the volume serial number.
+ ///
+ public int VolumeId
+ {
+ get { return (int)_bsVolId; }
+ }
+
+ ///
+ /// Gets the volume label.
+ ///
+ public override string VolumeLabel
+ {
+ get
+ {
+ long volId = _rootDir.FindVolumeId();
+ if (volId < 0)
+ {
+ return _bsVolLab;
+ }
+ else
+ {
+ return _rootDir.GetEntry(volId).Name.GetRawName(FatOptions.FileNameEncoding);
+ }
+ }
+ }
+
+ ///
+ /// Gets the (informational only) file system type recorded in the meta-data.
+ ///
+ public string FileSystemType
+ {
+ get { return _bsFilSysType; }
+ }
+
+ ///
+ /// Gets the active FAT (zero-based index).
+ ///
+ public byte ActiveFat
+ {
+ get { return (byte)(((_bpbExtFlags & 0x08) != 0) ? _bpbExtFlags & 0x7 : 0); }
+ }
+
+ ///
+ /// Gets a value indicating whether FAT changes are mirrored to all copies of the FAT.
+ ///
+ public bool MirrorFat
+ {
+ get { return (_bpbExtFlags & 0x08) == 0; }
+ }
+
+ ///
+ /// Gets the file-system version (usually 0).
+ ///
+ public int Version
+ {
+ get { return _bpbFSVer; }
+ }
+
+ ///
+ /// Gets the cluster number of the first cluster of the root directory (FAT32 only).
+ ///
+ public long RootDirectoryCluster
+ {
+ get { return _bpbRootClus; }
+ }
+
+ ///
+ /// Gets the sector location of the FSINFO structure (FAT32 only).
+ ///
+ public int FSInfoSector
+ {
+ get { return _bpbFSInfo; }
+ }
+
+ ///
+ /// Gets the Sector location of the backup boot sector (FAT32 only).
+ ///
+ public int BackupBootSector
+ {
+ get { return _bpbBkBootSec; }
+ }
+
+ ///
+ /// Indicates if this file system is read-only or read-write.
+ ///
+ /// .
+ public override bool CanWrite
+ {
+ get { return _data.CanWrite; }
+ }
+
+ internal FileAllocationTable Fat
+ {
+ get { return _fat; }
+ }
+
+ internal ClusterReader ClusterReader
+ {
+ get { return _clusterReader; }
+ }
+
+ #region Disk Formatting
+ ///
+ /// Creates a formatted floppy disk image in a stream.
+ ///
+ /// The stream to write the blank image to.
+ /// The type of floppy to create.
+ /// The volume label for the floppy (or null).
+ /// An object that provides access to the newly created floppy disk image.
+ public static FatFileSystem FormatFloppy(Stream stream, FloppyDiskType type, string label)
+ {
+ long pos = stream.Position;
+
+ long ticks = DateTime.UtcNow.Ticks;
+ uint volId = (uint)((ticks & 0xFFFF) | (ticks >> 32));
+
+ // Write the BIOS Parameter Block (BPB) - a single sector
+ byte[] bpb = new byte[512];
+ uint sectors;
+ if (type == FloppyDiskType.DoubleDensity)
+ {
+ sectors = 1440;
+ WriteBPB(bpb, sectors, FatType.Fat12, 224, 0, 1, 1, new Geometry(80, 2, 9), true, volId, label);
+ }
+ else if (type == FloppyDiskType.HighDensity)
+ {
+ sectors = 2880;
+ WriteBPB(bpb, sectors, FatType.Fat12, 224, 0, 1, 1, new Geometry(80, 2, 18), true, volId, label);
+ }
+ else if (type == FloppyDiskType.Extended)
+ {
+ sectors = 5760;
+ WriteBPB(bpb, sectors, FatType.Fat12, 224, 0, 1, 1, new Geometry(80, 2, 36), true, volId, label);
+ }
+ else
+ {
+ throw new ArgumentException("Unrecognised Floppy Disk type", "type");
+ }
+
+ stream.Write(bpb, 0, bpb.Length);
+
+ // Write both FAT copies
+ uint fatSize = CalcFatSize(sectors, FatType.Fat12, 1);
+ byte[] fat = new byte[fatSize * Sizes.Sector];
+ FatBuffer fatBuffer = new FatBuffer(FatType.Fat12, fat);
+ fatBuffer.SetNext(0, 0xFFFFFFF0);
+ fatBuffer.SetEndOfChain(1);
+ stream.Write(fat, 0, fat.Length);
+ stream.Write(fat, 0, fat.Length);
+
+ // Write the (empty) root directory
+ uint rootDirSectors = ((224 * 32) + Sizes.Sector - 1) / Sizes.Sector;
+ byte[] rootDir = new byte[rootDirSectors * Sizes.Sector];
+ stream.Write(rootDir, 0, rootDir.Length);
+
+ // Write a single byte at the end of the disk to ensure the stream is at least as big
+ // as needed for this disk image.
+ stream.Position = pos + (sectors * Sizes.Sector) - 1;
+ stream.WriteByte(0);
+
+ // Give the caller access to the new file system
+ stream.Position = pos;
+ return new FatFileSystem(stream);
+ }
+
+ ///
+ /// Formats a virtual hard disk partition.
+ ///
+ /// The disk containing the partition.
+ /// The index of the partition on the disk.
+ /// The volume label for the partition (or null).
+ /// An object that provides access to the newly created partition file system.
+ public static FatFileSystem FormatPartition(VirtualDisk disk, int partitionIndex, string label)
+ {
+ using (Stream partitionStream = disk.Partitions[partitionIndex].Open())
+ {
+ return FormatPartition(
+ partitionStream,
+ label,
+ disk.Geometry,
+ (int)disk.Partitions[partitionIndex].FirstSector,
+ (int)(1 + disk.Partitions[partitionIndex].LastSector - disk.Partitions[partitionIndex].FirstSector),
+ 0);
+ }
+ }
+
+ ///
+ /// Creates a formatted hard disk partition in a stream.
+ ///
+ /// The stream to write the new file system to.
+ /// The volume label for the partition (or null).
+ /// The geometry of the disk containing the partition.
+ /// The starting sector number of this partition (hide's sectors in other partitions).
+ /// The number of sectors in this partition.
+ /// The number of reserved sectors at the start of the partition.
+ /// An object that provides access to the newly created partition file system.
+ public static FatFileSystem FormatPartition(
+ Stream stream,
+ string label,
+ Geometry diskGeometry,
+ int firstSector,
+ int sectorCount,
+ short reservedSectors)
+ {
+ long pos = stream.Position;
+
+ long ticks = DateTime.UtcNow.Ticks;
+ uint volId = (uint)((ticks & 0xFFFF) | (ticks >> 32));
+
+ byte sectorsPerCluster;
+ FatType fatType;
+ ushort maxRootEntries;
+
+ /*
+ * Write the BIOS Parameter Block (BPB) - a single sector
+ */
+
+ byte[] bpb = new byte[512];
+ if (sectorCount <= 8400)
+ {
+ throw new ArgumentException("Requested size is too small for a partition");
+ }
+ else if (sectorCount < 1024 * 1024)
+ {
+ fatType = FatType.Fat16;
+ maxRootEntries = 512;
+ if (sectorCount <= 32680)
+ {
+ sectorsPerCluster = 2;
+ }
+ else if (sectorCount <= 262144)
+ {
+ sectorsPerCluster = 4;
+ }
+ else if (sectorCount <= 524288)
+ {
+ sectorsPerCluster = 8;
+ }
+ else
+ {
+ sectorsPerCluster = 16;
+ }
+
+ if (reservedSectors < 1)
+ {
+ reservedSectors = 1;
+ }
+ }
+ else
+ {
+ fatType = FatType.Fat32;
+ maxRootEntries = 0;
+ if (sectorCount <= 532480)
+ {
+ sectorsPerCluster = 1;
+ }
+ else if (sectorCount <= 16777216)
+ {
+ sectorsPerCluster = 8;
+ }
+ else if (sectorCount <= 33554432)
+ {
+ sectorsPerCluster = 16;
+ }
+ else if (sectorCount <= 67108864)
+ {
+ sectorsPerCluster = 32;
+ }
+ else
+ {
+ sectorsPerCluster = 64;
+ }
+
+ if (reservedSectors < 32)
+ {
+ reservedSectors = 32;
+ }
+ }
+
+ WriteBPB(bpb, (uint)sectorCount, fatType, maxRootEntries, (uint)firstSector, (ushort)reservedSectors, sectorsPerCluster, diskGeometry, false, volId, label);
+ stream.Write(bpb, 0, bpb.Length);
+
+ /*
+ * Skip the reserved sectors
+ */
+
+ stream.Position = pos + (((ushort)reservedSectors) * Sizes.Sector);
+
+ /*
+ * Write both FAT copies
+ */
+
+ byte[] fat = new byte[CalcFatSize((uint)sectorCount, fatType, sectorsPerCluster) * Sizes.Sector];
+ FatBuffer fatBuffer = new FatBuffer(fatType, fat);
+ fatBuffer.SetNext(0, 0xFFFFFFF8);
+ fatBuffer.SetEndOfChain(1);
+ if (fatType >= FatType.Fat32)
+ {
+ // Mark cluster 2 as End-of-chain (i.e. root directory
+ // is a single cluster in length)
+ fatBuffer.SetEndOfChain(2);
+ }
+
+ stream.Write(fat, 0, fat.Length);
+ stream.Write(fat, 0, fat.Length);
+
+ /*
+ * Write the (empty) root directory
+ */
+
+ uint rootDirSectors;
+ if (fatType < FatType.Fat32)
+ {
+ rootDirSectors = (uint)(((maxRootEntries * 32) + Sizes.Sector - 1) / Sizes.Sector);
+ }
+ else
+ {
+ rootDirSectors = sectorsPerCluster;
+ }
+
+ byte[] rootDir = new byte[rootDirSectors * Sizes.Sector];
+ stream.Write(rootDir, 0, rootDir.Length);
+
+ /*
+ * Make sure the stream is at least as large as the partition requires.
+ */
+
+ if (stream.Length < pos + (sectorCount * Sizes.Sector))
+ {
+ stream.SetLength(pos + (sectorCount * Sizes.Sector));
+ }
+
+ /*
+ * Give the caller access to the new file system
+ */
+
+ stream.Position = pos;
+ return new FatFileSystem(stream);
+ }
+ #endregion
+
+ ///
+ /// Detects if a stream contains a FAT file system.
+ ///
+ /// The stream to inspect.
+ /// true if the stream appears to be a FAT file system, else false.
+ public static bool Detect(Stream stream)
+ {
+ if (stream.Length < 512)
+ {
+ return false;
+ }
+
+ stream.Position = 0;
+ byte[] bytes = StreamUtilities.ReadExact(stream, 512);
+ ushort bpbBytesPerSec = EndianUtilities.ToUInt16LittleEndian(bytes, 11);
+ if (bpbBytesPerSec != 512)
+ {
+ return false;
+ }
+
+ byte bpbNumFATs = bytes[16];
+ if (bpbNumFATs == 0 || bpbNumFATs > 2)
+ {
+ return false;
+ }
+
+ ushort bpbTotSec16 = EndianUtilities.ToUInt16LittleEndian(bytes, 19);
+ uint bpbTotSec32 = EndianUtilities.ToUInt32LittleEndian(bytes, 32);
+
+ if (!((bpbTotSec16 == 0) ^ (bpbTotSec32 == 0)))
+ {
+ return false;
+ }
+
+ uint totalSectors = bpbTotSec16 + bpbTotSec32;
+ return totalSectors * (long)bpbBytesPerSec <= stream.Length;
+ }
+
+ ///
+ /// Opens a file for reading and/or writing.
+ ///
+ /// The full path to the file.
+ /// The file mode.
+ /// The desired access.
+ /// The stream to the opened file.
+ public override SparseStream OpenFile(string path, FileMode mode, FileAccess access)
+ {
+ Directory parent;
+ long entryId;
+ try
+ {
+ entryId = GetDirectoryEntry(_rootDir, path, out parent);
+ }
+ catch (ArgumentException)
+ {
+ throw new IOException("Invalid path: " + path);
+ }
+
+ if (parent == null)
+ {
+ throw new FileNotFoundException("Could not locate file", path);
+ }
+
+ if (entryId < 0)
+ {
+ return parent.OpenFile(FileName.FromPath(path, FatOptions.FileNameEncoding), mode, access);
+ }
+
+ DirectoryEntry dirEntry = parent.GetEntry(entryId);
+
+ if ((dirEntry.Attributes & FatAttributes.Directory) != 0)
+ {
+ throw new IOException("Attempt to open directory as a file");
+ }
+ else
+ {
+ return parent.OpenFile(dirEntry.Name, mode, access);
+ }
+ }
+
+ ///
+ /// Gets the attributes of a file or directory.
+ ///
+ /// The file or directory to inspect.
+ /// The attributes of the file or directory.
+ public override FileAttributes GetAttributes(string path)
+ {
+ // Simulate a root directory entry - doesn't really exist though
+ if (IsRootPath(path))
+ {
+ return FileAttributes.Directory;
+ }
+
+ DirectoryEntry dirEntry = GetDirectoryEntry(path);
+ if (dirEntry == null)
+ {
+ throw new FileNotFoundException("No such file", path);
+ }
+
+ // Luckily, FAT and .NET FileAttributes match, bit-for-bit
+ return (FileAttributes)dirEntry.Attributes;
+ }
+
+ ///
+ /// Sets the attributes of a file or directory.
+ ///
+ /// The file or directory to change.
+ /// The new attributes of the file or directory.
+ public override void SetAttributes(string path, FileAttributes newValue)
+ {
+ if (IsRootPath(path))
+ {
+ if (newValue != FileAttributes.Directory)
+ {
+ throw new NotSupportedException("The attributes of the root directory cannot be modified");
+ }
+
+ return;
+ }
+
+ Directory parent;
+ long id = GetDirectoryEntry(path, out parent);
+ DirectoryEntry dirEntry = parent.GetEntry(id);
+
+ FatAttributes newFatAttr = (FatAttributes)newValue;
+
+ if ((newFatAttr & FatAttributes.Directory) != (dirEntry.Attributes & FatAttributes.Directory))
+ {
+ throw new ArgumentException("Attempted to change the directory attribute");
+ }
+
+ dirEntry.Attributes = newFatAttr;
+ parent.UpdateEntry(id, dirEntry);
+
+ // For directories, need to update their 'self' entry also
+ if ((dirEntry.Attributes & FatAttributes.Directory) != 0)
+ {
+ Directory dir = GetDirectory(path);
+ dirEntry = dir.SelfEntry;
+ dirEntry.Attributes = newFatAttr;
+ dir.SelfEntry = dirEntry;
+ }
+ }
+
+ ///
+ /// Gets the creation time (in local time) of a file or directory.
+ ///
+ /// The path of the file or directory.
+ /// The creation time.
+ public override DateTime GetCreationTime(string path)
+ {
+ if (IsRootPath(path))
+ {
+ return Epoch;
+ }
+
+ return GetDirectoryEntry(path).CreationTime;
+ }
+
+ ///
+ /// Sets the creation time (in local time) of a file or directory.
+ ///
+ /// The path of the file or directory.
+ /// The new time to set.
+ public override void SetCreationTime(string path, DateTime newTime)
+ {
+ if (IsRootPath(path))
+ {
+ if (newTime != Epoch)
+ {
+ throw new NotSupportedException("The creation time of the root directory cannot be modified");
+ }
+
+ return;
+ }
+
+ UpdateDirEntry(path, (e) => { e.CreationTime = newTime; });
+ }
+
+ ///
+ /// Gets the creation time (in UTC) of a file or directory.
+ ///
+ /// The path of the file or directory.
+ /// The creation time.
+ public override DateTime GetCreationTimeUtc(string path)
+ {
+ if (IsRootPath(path))
+ {
+ return ConvertToUtc(Epoch);
+ }
+
+ return ConvertToUtc(GetDirectoryEntry(path).CreationTime);
+ }
+
+ ///
+ /// Sets the creation time (in UTC) of a file or directory.
+ ///
+ /// The path of the file or directory.
+ /// The new time to set.
+ public override void SetCreationTimeUtc(string path, DateTime newTime)
+ {
+ if (IsRootPath(path))
+ {
+ if (ConvertFromUtc(newTime) != Epoch)
+ {
+ throw new NotSupportedException("The last write time of the root directory cannot be modified");
+ }
+
+ return;
+ }
+
+ UpdateDirEntry(path, (e) => { e.CreationTime = ConvertFromUtc(newTime); });
+ }
+
+ ///
+ /// Gets the last access time (in local time) of a file or directory.
+ ///
+ /// The path of the file or directory.
+ /// The time the file or directory was last accessed.
+ public override DateTime GetLastAccessTime(string path)
+ {
+ if (IsRootPath(path))
+ {
+ return Epoch;
+ }
+
+ return GetDirectoryEntry(path).LastAccessTime;
+ }
+
+ ///
+ /// Sets the last access time (in local time) of a file or directory.
+ ///
+ /// The path of the file or directory.
+ /// The new time to set.
+ public override void SetLastAccessTime(string path, DateTime newTime)
+ {
+ if (IsRootPath(path))
+ {
+ if (newTime != Epoch)
+ {
+ throw new NotSupportedException("The last access time of the root directory cannot be modified");
+ }
+
+ return;
+ }
+
+ UpdateDirEntry(path, (e) => { e.LastAccessTime = newTime; });
+ }
+
+ ///
+ /// Gets the last access time (in UTC) of a file or directory.
+ ///
+ /// The path of the file or directory.
+ /// The time the file or directory was last accessed.
+ public override DateTime GetLastAccessTimeUtc(string path)
+ {
+ if (IsRootPath(path))
+ {
+ return ConvertToUtc(Epoch);
+ }
+
+ return ConvertToUtc(GetDirectoryEntry(path).LastAccessTime);
+ }
+
+ ///
+ /// Sets the last access time (in UTC) of a file or directory.
+ ///
+ /// The path of the file or directory.
+ /// The new time to set.
+ public override void SetLastAccessTimeUtc(string path, DateTime newTime)
+ {
+ if (IsRootPath(path))
+ {
+ if (ConvertFromUtc(newTime) != Epoch)
+ {
+ throw new NotSupportedException("The last write time of the root directory cannot be modified");
+ }
+
+ return;
+ }
+
+ UpdateDirEntry(path, (e) => { e.LastAccessTime = ConvertFromUtc(newTime); });
+ }
+
+ ///
+ /// Gets the last modification time (in local time) of a file or directory.
+ ///
+ /// The path of the file or directory.
+ /// The time the file or directory was last modified.
+ public override DateTime GetLastWriteTime(string path)
+ {
+ if (IsRootPath(path))
+ {
+ return Epoch;
+ }
+
+ return GetDirectoryEntry(path).LastWriteTime;
+ }
+
+ ///
+ /// Sets the last modification time (in local time) of a file or directory.
+ ///
+ /// The path of the file or directory.
+ /// The new time to set.
+ public override void SetLastWriteTime(string path, DateTime newTime)
+ {
+ if (IsRootPath(path))
+ {
+ if (newTime != Epoch)
+ {
+ throw new NotSupportedException("The last write time of the root directory cannot be modified");
+ }
+
+ return;
+ }
+
+ UpdateDirEntry(path, (e) => { e.LastWriteTime = newTime; });
+ }
+
+ ///
+ /// Gets the last modification time (in UTC) of a file or directory.
+ ///
+ /// The path of the file or directory.
+ /// The time the file or directory was last modified.
+ public override DateTime GetLastWriteTimeUtc(string path)
+ {
+ if (IsRootPath(path))
+ {
+ return ConvertToUtc(Epoch);
+ }
+
+ return ConvertToUtc(GetDirectoryEntry(path).LastWriteTime);
+ }
+
+ ///
+ /// Sets the last modification time (in UTC) of a file or directory.
+ ///
+ /// The path of the file or directory.
+ /// The new time to set.
+ public override void SetLastWriteTimeUtc(string path, DateTime newTime)
+ {
+ if (IsRootPath(path))
+ {
+ if (ConvertFromUtc(newTime) != Epoch)
+ {
+ throw new NotSupportedException("The last write time of the root directory cannot be modified");
+ }
+
+ return;
+ }
+
+ UpdateDirEntry(path, (e) => { e.LastWriteTime = ConvertFromUtc(newTime); });
+ }
+
+ ///
+ /// Gets the length of a file.
+ ///
+ /// The path to the file.
+ /// The length in bytes.
+ public override long GetFileLength(string path)
+ {
+ return GetDirectoryEntry(path).FileSize;
+ }
+
+ ///
+ /// Copies an existing file to a new file, allowing overwriting of an existing file.
+ ///
+ /// The source file.
+ /// The destination file.
+ /// Whether to permit over-writing of an existing file.
+ public override void CopyFile(string sourceFile, string destinationFile, bool overwrite)
+ {
+ Directory sourceDir;
+ long sourceEntryId = GetDirectoryEntry(sourceFile, out sourceDir);
+
+ if (sourceDir == null || sourceEntryId < 0)
+ {
+ throw new FileNotFoundException(string.Format(CultureInfo.InvariantCulture, "The source file '{0}' was not found", sourceFile));
+ }
+
+ DirectoryEntry sourceEntry = sourceDir.GetEntry(sourceEntryId);
+
+ if ((sourceEntry.Attributes & FatAttributes.Directory) != 0)
+ {
+ throw new IOException("The source file is a directory");
+ }
+
+ DirectoryEntry newEntry = new DirectoryEntry(sourceEntry);
+ newEntry.Name = FileName.FromPath(destinationFile, FatOptions.FileNameEncoding);
+ newEntry.FirstCluster = 0;
+
+ Directory destDir;
+ long destEntryId = GetDirectoryEntry(destinationFile, out destDir);
+
+ if (destDir == null)
+ {
+ throw new DirectoryNotFoundException(string.Format(CultureInfo.InvariantCulture, "The destination directory for '{0}' was not found", destinationFile));
+ }
+
+ // If the destination is a directory, use the old file name to construct a full path.
+ if (destEntryId >= 0)
+ {
+ DirectoryEntry destEntry = destDir.GetEntry(destEntryId);
+ if ((destEntry.Attributes & FatAttributes.Directory) != 0)
+ {
+ newEntry.Name = FileName.FromPath(sourceFile, FatOptions.FileNameEncoding);
+ destinationFile = Utilities.CombinePaths(destinationFile, Utilities.GetFileFromPath(sourceFile));
+
+ destEntryId = GetDirectoryEntry(destinationFile, out destDir);
+ }
+ }
+
+ // If there's an existing entry...
+ if (destEntryId >= 0)
+ {
+ DirectoryEntry destEntry = destDir.GetEntry(destEntryId);
+
+ if ((destEntry.Attributes & FatAttributes.Directory) != 0)
+ {
+ throw new IOException("Destination file is an existing directory");
+ }
+
+ if (!overwrite)
+ {
+ throw new IOException("Destination file already exists");
+ }
+
+ // Remove the old file
+ destDir.DeleteEntry(destEntryId, true);
+ }
+
+ // Add the new file's entry
+ destEntryId = destDir.AddEntry(newEntry);
+
+ // Copy the contents...
+ using (Stream sourceStream = new FatFileStream(this, sourceDir, sourceEntryId, FileAccess.Read),
+ destStream = new FatFileStream(this, destDir, destEntryId, FileAccess.Write))
+ {
+ StreamUtilities.PumpStreams(sourceStream, destStream);
+ }
+ }
+
+ ///
+ /// Creates a directory.
+ ///
+ /// The directory to create.
+ public override void CreateDirectory(string path)
+ {
+ string[] pathElements = path.Split(new char[] { '\\' }, StringSplitOptions.RemoveEmptyEntries);
+
+ Directory focusDir = _rootDir;
+
+ for (int i = 0; i < pathElements.Length; ++i)
+ {
+ FileName name;
+ try
+ {
+ name = new FileName(pathElements[i], FatOptions.FileNameEncoding);
+ }
+ catch (ArgumentException ae)
+ {
+ throw new IOException("Invalid path", ae);
+ }
+
+ Directory child = focusDir.GetChildDirectory(name);
+ if (child == null)
+ {
+ child = focusDir.CreateChildDirectory(name);
+ }
+
+ focusDir = child;
+ }
+ }
+
+ ///
+ /// Deletes a directory, optionally with all descendants.
+ ///
+ /// The path of the directory to delete.
+ public override void DeleteDirectory(string path)
+ {
+ Directory dir = GetDirectory(path);
+ if (dir == null)
+ {
+ throw new DirectoryNotFoundException(String.Format(CultureInfo.InvariantCulture, "No such directory: {0}", path));
+ }
+
+ if (!dir.IsEmpty)
+ {
+ throw new IOException("Unable to delete non-empty directory");
+ }
+
+ Directory parent;
+ long id = GetDirectoryEntry(path, out parent);
+ if (parent == null && id == 0)
+ {
+ throw new IOException("Unable to delete root directory");
+ }
+ else if (parent != null && id >= 0)
+ {
+ DirectoryEntry deadEntry = parent.GetEntry(id);
+ parent.DeleteEntry(id, true);
+ ForgetDirectory(deadEntry);
+ }
+ else
+ {
+ throw new DirectoryNotFoundException("No such directory: " + path);
+ }
+ }
+
+ ///
+ /// Deletes a file.
+ ///
+ /// The path of the file to delete.
+ public override void DeleteFile(string path)
+ {
+ Directory parent;
+ long id = GetDirectoryEntry(path, out parent);
+ if (parent == null || id < 0)
+ {
+ throw new FileNotFoundException("No such file", path);
+ }
+
+ DirectoryEntry entry = parent.GetEntry(id);
+ if (entry == null || (entry.Attributes & FatAttributes.Directory) != 0)
+ {
+ throw new FileNotFoundException("No such file", path);
+ }
+
+ parent.DeleteEntry(id, true);
+ }
+
+ ///
+ /// Indicates if a directory exists.
+ ///
+ /// The path to test.
+ /// true if the directory exists.
+ public override bool DirectoryExists(string path)
+ {
+ // Special case - root directory
+ if (String.IsNullOrEmpty(path))
+ {
+ return true;
+ }
+ else
+ {
+ DirectoryEntry dirEntry = GetDirectoryEntry(path);
+ return dirEntry != null && (dirEntry.Attributes & FatAttributes.Directory) != 0;
+ }
+ }
+
+ ///
+ /// Indicates if a file exists.
+ ///
+ /// The path to test.
+ /// true if the file exists.
+ public override bool FileExists(string path)
+ {
+ // Special case - root directory
+ if (String.IsNullOrEmpty(path))
+ {
+ return true;
+ }
+ else
+ {
+ DirectoryEntry dirEntry = GetDirectoryEntry(path);
+ return dirEntry != null && (dirEntry.Attributes & FatAttributes.Directory) == 0;
+ }
+ }
+
+ ///
+ /// Indicates if a file or directory exists.
+ ///
+ /// The path to test.
+ /// true if the file or directory exists.
+ public override bool Exists(string path)
+ {
+ // Special case - root directory
+ if (String.IsNullOrEmpty(path))
+ {
+ return true;
+ }
+ else
+ {
+ return GetDirectoryEntry(path) != null;
+ }
+ }
+
+ ///
+ /// Gets the names of subdirectories in a specified directory.
+ ///
+ /// The path to search.
+ /// Array of directories.
+ public override string[] GetDirectories(string path)
+ {
+ Directory dir = GetDirectory(path);
+ if (dir == null)
+ {
+ throw new DirectoryNotFoundException(string.Format(CultureInfo.InvariantCulture, "The directory '{0}' was not found", path));
+ }
+
+ DirectoryEntry[] entries = dir.GetDirectories();
+ List dirs = new List(entries.Length);
+ foreach (DirectoryEntry dirEntry in entries)
+ {
+ dirs.Add(Utilities.CombinePaths(path, dirEntry.Name.GetDisplayName(FatOptions.FileNameEncoding)));
+ }
+
+ return dirs.ToArray();
+ }
+
+ ///
+ /// Gets the names of subdirectories in a specified directory matching a specified
+ /// search pattern, using a value to determine whether to search subdirectories.
+ ///
+ /// The path to search.
+ /// The search string to match against.
+ /// Indicates whether to search subdirectories.
+ /// Array of directories matching the search pattern.
+ public override string[] GetDirectories(string path, string searchPattern, SearchOption searchOption)
+ {
+ Regex re = Utilities.ConvertWildcardsToRegEx(searchPattern);
+
+ List dirs = new List();
+ DoSearch(dirs, path, re, searchOption == SearchOption.AllDirectories, true, false);
+ return dirs.ToArray();
+ }
+
+ ///
+ /// Gets the names of files in a specified directory.
+ ///
+ /// The path to search.
+ /// Array of files.
+ public override string[] GetFiles(string path)
+ {
+ Directory dir = GetDirectory(path);
+ DirectoryEntry[] entries = dir.GetFiles();
+
+ List files = new List(entries.Length);
+ foreach (DirectoryEntry dirEntry in entries)
+ {
+ files.Add(Utilities.CombinePaths(path, dirEntry.Name.GetDisplayName(FatOptions.FileNameEncoding)));
+ }
+
+ return files.ToArray();
+ }
+
+ ///
+ /// Gets the names of files in a specified directory matching a specified
+ /// search pattern, using a value to determine whether to search subdirectories.
+ ///
+ /// The path to search.
+ /// The search string to match against.
+ /// Indicates whether to search subdirectories.
+ /// Array of files matching the search pattern.
+ public override string[] GetFiles(string path, string searchPattern, SearchOption searchOption)
+ {
+ Regex re = Utilities.ConvertWildcardsToRegEx(searchPattern);
+
+ List results = new List();
+ DoSearch(results, path, re, searchOption == SearchOption.AllDirectories, false, true);
+ return results.ToArray();
+ }
+
+ ///
+ /// Gets the names of all files and subdirectories in a specified directory.
+ ///
+ /// The path to search.
+ /// Array of files and subdirectories matching the search pattern.
+ public override string[] GetFileSystemEntries(string path)
+ {
+ Directory dir = GetDirectory(path);
+ DirectoryEntry[] entries = dir.Entries;
+
+ List result = new List(entries.Length);
+ foreach (DirectoryEntry dirEntry in entries)
+ {
+ result.Add(Utilities.CombinePaths(path, dirEntry.Name.GetDisplayName(FatOptions.FileNameEncoding)));
+ }
+
+ return result.ToArray();
+ }
+
+ ///
+ /// Gets the names of files and subdirectories in a specified directory matching a specified
+ /// search pattern.
+ ///
+ /// The path to search.
+ /// The search string to match against.
+ /// Array of files and subdirectories matching the search pattern.
+ public override string[] GetFileSystemEntries(string path, string searchPattern)
+ {
+ Regex re = Utilities.ConvertWildcardsToRegEx(searchPattern);
+
+ Directory dir = GetDirectory(path);
+ DirectoryEntry[] entries = dir.Entries;
+
+ List result = new List(entries.Length);
+ foreach (DirectoryEntry dirEntry in entries)
+ {
+ if (re.IsMatch(dirEntry.Name.GetSearchName(FatOptions.FileNameEncoding)))
+ {
+ result.Add(Utilities.CombinePaths(path, dirEntry.Name.GetDisplayName(FatOptions.FileNameEncoding)));
+ }
+ }
+
+ return result.ToArray();
+ }
+
+ ///
+ /// Moves a directory.
+ ///
+ /// The directory to move.
+ /// The target directory name.
+ public override void MoveDirectory(string sourceDirectoryName, string destinationDirectoryName)
+ {
+ if (string.IsNullOrEmpty(destinationDirectoryName))
+ {
+ if (destinationDirectoryName == null)
+ {
+ throw new ArgumentNullException("destinationDirectoryName");
+ }
+ else
+ {
+ throw new ArgumentException("Invalid destination name (empty string)");
+ }
+ }
+
+ Directory destParent;
+ long destId = GetDirectoryEntry(destinationDirectoryName, out destParent);
+ if (destParent == null)
+ {
+ throw new DirectoryNotFoundException("Target directory doesn't exist");
+ }
+ else if (destId >= 0)
+ {
+ throw new IOException("Target directory already exists");
+ }
+
+ Directory sourceParent;
+ long sourceId = GetDirectoryEntry(sourceDirectoryName, out sourceParent);
+ if (sourceParent == null || sourceId < 0)
+ {
+ throw new IOException("Source directory doesn't exist");
+ }
+
+ destParent.AttachChildDirectory(FileName.FromPath(destinationDirectoryName, FatOptions.FileNameEncoding), GetDirectory(sourceDirectoryName));
+ sourceParent.DeleteEntry(sourceId, false);
+ }
+
+ ///
+ /// Moves a file, allowing an existing file to be overwritten.
+ ///
+ /// The file to move.
+ /// The target file name.
+ /// Whether to permit a destination file to be overwritten.
+ public override void MoveFile(string sourceName, string destinationName, bool overwrite)
+ {
+ Directory sourceDir;
+ long sourceEntryId = GetDirectoryEntry(sourceName, out sourceDir);
+
+ if (sourceDir == null || sourceEntryId < 0)
+ {
+ throw new FileNotFoundException(string.Format(CultureInfo.InvariantCulture, "The source file '{0}' was not found", sourceName));
+ }
+
+ DirectoryEntry sourceEntry = sourceDir.GetEntry(sourceEntryId);
+
+ if ((sourceEntry.Attributes & FatAttributes.Directory) != 0)
+ {
+ throw new IOException("The source file is a directory");
+ }
+
+ DirectoryEntry newEntry = new DirectoryEntry(sourceEntry);
+ newEntry.Name = FileName.FromPath(destinationName, FatOptions.FileNameEncoding);
+
+ Directory destDir;
+ long destEntryId = GetDirectoryEntry(destinationName, out destDir);
+
+ if (destDir == null)
+ {
+ throw new DirectoryNotFoundException(string.Format(CultureInfo.InvariantCulture, "The destination directory for '{0}' was not found", destinationName));
+ }
+
+ // If the destination is a directory, use the old file name to construct a full path.
+ if (destEntryId >= 0)
+ {
+ DirectoryEntry destEntry = destDir.GetEntry(destEntryId);
+ if ((destEntry.Attributes & FatAttributes.Directory) != 0)
+ {
+ newEntry.Name = FileName.FromPath(sourceName, FatOptions.FileNameEncoding);
+ destinationName = Utilities.CombinePaths(destinationName, Utilities.GetFileFromPath(sourceName));
+
+ destEntryId = GetDirectoryEntry(destinationName, out destDir);
+ }
+ }
+
+ // If there's an existing entry...
+ if (destEntryId >= 0)
+ {
+ DirectoryEntry destEntry = destDir.GetEntry(destEntryId);
+
+ if ((destEntry.Attributes & FatAttributes.Directory) != 0)
+ {
+ throw new IOException("Destination file is an existing directory");
+ }
+
+ if (!overwrite)
+ {
+ throw new IOException("Destination file already exists");
+ }
+
+ // Remove the old file
+ destDir.DeleteEntry(destEntryId, true);
+ }
+
+ // Add the new file's entry and remove the old link to the file's contents
+ destDir.AddEntry(newEntry);
+ sourceDir.DeleteEntry(sourceEntryId, false);
+ }
+
+ internal DateTime ConvertToUtc(DateTime dateTime)
+ {
+ return _timeConverter(dateTime, true);
+ }
+
+ internal DateTime ConvertFromUtc(DateTime dateTime)
+ {
+ return _timeConverter(dateTime, false);
+ }
+
+ internal Directory GetDirectory(string path)
+ {
+ Directory parent;
+
+ if (string.IsNullOrEmpty(path) || path == "\\")
+ {
+ return _rootDir;
+ }
+
+ long id = GetDirectoryEntry(_rootDir, path, out parent);
+ if (id >= 0)
+ {
+ return GetDirectory(parent, id);
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ internal Directory GetDirectory(Directory parent, long parentId)
+ {
+ if (parent == null)
+ {
+ return _rootDir;
+ }
+
+ DirectoryEntry dirEntry = parent.GetEntry(parentId);
+ if ((dirEntry.Attributes & FatAttributes.Directory) == 0)
+ {
+ throw new DirectoryNotFoundException();
+ }
+
+ // If we have this one cached, return it
+ Directory result;
+ if (_dirCache.TryGetValue(dirEntry.FirstCluster, out result))
+ {
+ return result;
+ }
+
+ // Not cached - create a new one.
+ result = new Directory(parent, parentId);
+ _dirCache[dirEntry.FirstCluster] = result;
+ return result;
+ }
+
+ internal void ForgetDirectory(DirectoryEntry entry)
+ {
+ uint index = entry.FirstCluster;
+ if (index != 0 && _dirCache.ContainsKey(index))
+ {
+ Directory dir = _dirCache[index];
+ _dirCache.Remove(index);
+ dir.Dispose();
+ }
+ }
+
+ internal DirectoryEntry GetDirectoryEntry(string path)
+ {
+ Directory parent;
+
+ long id = GetDirectoryEntry(_rootDir, path, out parent);
+ if (parent == null || id < 0)
+ {
+ return null;
+ }
+
+ return parent.GetEntry(id);
+ }
+
+ internal long GetDirectoryEntry(string path, out Directory parent)
+ {
+ return GetDirectoryEntry(_rootDir, path, out parent);
+ }
+
+ ///
+ /// Disposes of this instance.
+ ///
+ /// The value true if Disposing.
+ protected override void Dispose(bool disposing)
+ {
+ try
+ {
+ if (disposing)
+ {
+ foreach (Directory dir in _dirCache.Values)
+ {
+ dir.Dispose();
+ }
+
+ _rootDir.Dispose();
+
+ if (_ownsData == Ownership.Dispose)
+ {
+ _data.Dispose();
+ _data = null;
+ }
+ }
+ }
+ finally
+ {
+ base.Dispose(disposing);
+ }
+ }
+
+ ///
+ /// Writes a FAT12/FAT16 BPB.
+ ///
+ /// The buffer to fill.
+ /// The total capacity of the disk (in sectors).
+ /// The number of bits in each FAT entry.
+ /// The maximum number of root directory entries.
+ /// The number of hidden sectors before this file system (i.e. partition offset).
+ /// The number of reserved sectors before the FAT.
+ /// The number of sectors per cluster.
+ /// The geometry of the disk containing the Fat file system.
+ /// Indicates if the disk is a removable media (a floppy disk).
+ /// The disk's volume Id.
+ /// The disk's label (or null).
+ private static void WriteBPB(
+ byte[] bootSector,
+ uint sectors,
+ FatType fatType,
+ ushort maxRootEntries,
+ uint hiddenSectors,
+ ushort reservedSectors,
+ byte sectorsPerCluster,
+ Geometry diskGeometry,
+ bool isFloppy,
+ uint volId,
+ string label)
+ {
+ uint fatSectors = CalcFatSize(sectors, fatType, sectorsPerCluster);
+
+ bootSector[0] = 0xEB;
+ bootSector[1] = 0x3C;
+ bootSector[2] = 0x90;
+
+ // OEM Name
+ EndianUtilities.StringToBytes("DISCUTIL", bootSector, 3, 8);
+
+ // Bytes Per Sector (512)
+ bootSector[11] = 0;
+ bootSector[12] = 2;
+
+ // Sectors Per Cluster
+ bootSector[13] = sectorsPerCluster;
+
+ // Reserved Sector Count
+ EndianUtilities.WriteBytesLittleEndian(reservedSectors, bootSector, 14);
+
+ // Number of FATs
+ bootSector[16] = 2;
+
+ // Number of Entries in the root directory
+ EndianUtilities.WriteBytesLittleEndian((ushort)maxRootEntries, bootSector, 17);
+
+ // Total number of sectors (small)
+ EndianUtilities.WriteBytesLittleEndian((ushort)(sectors < 0x10000 ? sectors : 0), bootSector, 19);
+
+ // Media
+ bootSector[21] = (byte)(isFloppy ? 0xF0 : 0xF8);
+
+ // FAT size (FAT12/FAT16)
+ EndianUtilities.WriteBytesLittleEndian((ushort)(fatType < FatType.Fat32 ? fatSectors : 0), bootSector, 22);
+
+ // Sectors Per Track
+ EndianUtilities.WriteBytesLittleEndian((ushort)diskGeometry.SectorsPerTrack, bootSector, 24);
+
+ // Heads Per Cylinder
+ EndianUtilities.WriteBytesLittleEndian((ushort)diskGeometry.HeadsPerCylinder, bootSector, 26);
+
+ // Hidden Sectors
+ EndianUtilities.WriteBytesLittleEndian((uint)hiddenSectors, bootSector, 28);
+
+ // Total number of sectors (large)
+ EndianUtilities.WriteBytesLittleEndian((uint)(sectors >= 0x10000 ? sectors : 0), bootSector, 32);
+
+ if (fatType < FatType.Fat32)
+ {
+ WriteBS(bootSector, 36, isFloppy, volId, label, fatType);
+ }
+ else
+ {
+ // FAT size (FAT32)
+ EndianUtilities.WriteBytesLittleEndian((uint)fatSectors, bootSector, 36);
+
+ // Ext flags: 0x80 = FAT 1 (i.e. Zero) active, mirroring
+ bootSector[40] = 0x00;
+ bootSector[41] = 0x00;
+
+ // Filesystem version (0.0)
+ bootSector[42] = 0;
+ bootSector[43] = 0;
+
+ // First cluster of the root directory, always 2 since we don't do bad sectors...
+ EndianUtilities.WriteBytesLittleEndian((uint)2, bootSector, 44);
+
+ // Sector number of FSINFO
+ EndianUtilities.WriteBytesLittleEndian((uint)1, bootSector, 48);
+
+ // Sector number of the Backup Boot Sector
+ EndianUtilities.WriteBytesLittleEndian((uint)6, bootSector, 50);
+
+ // Reserved area - must be set to 0
+ Array.Clear(bootSector, 52, 12);
+
+ WriteBS(bootSector, 64, isFloppy, volId, label, fatType);
+ }
+
+ bootSector[510] = 0x55;
+ bootSector[511] = 0xAA;
+ }
+
+ private static uint CalcFatSize(uint sectors, FatType fatType, byte sectorsPerCluster)
+ {
+ uint numClusters = (uint)(sectors / sectorsPerCluster);
+ uint fatBytes = (numClusters * (ushort)fatType) / 8;
+ return (fatBytes + Sizes.Sector - 1) / Sizes.Sector;
+ }
+
+ private static void WriteBS(byte[] bootSector, int offset, bool isFloppy, uint volId, string label, FatType fatType)
+ {
+ if (string.IsNullOrEmpty(label))
+ {
+ label = "NO NAME ";
+ }
+
+ string fsType = "FAT32 ";
+ if (fatType == FatType.Fat12)
+ {
+ fsType = "FAT12 ";
+ }
+ else if (fatType == FatType.Fat16)
+ {
+ fsType = "FAT16 ";
+ }
+
+ // Drive Number (for BIOS)
+ bootSector[offset + 0] = (byte)(isFloppy ? 0x00 : 0x80);
+
+ // Reserved
+ bootSector[offset + 1] = 0;
+
+ // Boot Signature (indicates next 3 fields present)
+ bootSector[offset + 2] = 0x29;
+
+ // Volume Id
+ EndianUtilities.WriteBytesLittleEndian(volId, bootSector, offset + 3);
+
+ // Volume Label
+ EndianUtilities.StringToBytes(label + " ", bootSector, offset + 7, 11);
+
+ // File System Type
+ EndianUtilities.StringToBytes(fsType, bootSector, offset + 18, 8);
+ }
+
+ private static FatType DetectFATType(byte[] bpb)
+ {
+ uint bpbBytesPerSec = EndianUtilities.ToUInt16LittleEndian(bpb, 11);
+ uint bpbRootEntCnt = EndianUtilities.ToUInt16LittleEndian(bpb, 17);
+ uint bpbFATSz16 = EndianUtilities.ToUInt16LittleEndian(bpb, 22);
+ uint bpbFATSz32 = EndianUtilities.ToUInt32LittleEndian(bpb, 36);
+ uint bpbTotSec16 = EndianUtilities.ToUInt16LittleEndian(bpb, 19);
+ uint bpbTotSec32 = EndianUtilities.ToUInt32LittleEndian(bpb, 32);
+ uint bpbResvdSecCnt = EndianUtilities.ToUInt16LittleEndian(bpb, 14);
+ uint bpbNumFATs = bpb[16];
+ uint bpbSecPerClus = bpb[13];
+
+ uint rootDirSectors = ((bpbRootEntCnt * 32) + bpbBytesPerSec - 1) / bpbBytesPerSec;
+ uint fatSz = (bpbFATSz16 != 0) ? (uint)bpbFATSz16 : bpbFATSz32;
+ uint totalSec = (bpbTotSec16 != 0) ? (uint)bpbTotSec16 : bpbTotSec32;
+
+ uint dataSec = totalSec - (bpbResvdSecCnt + (bpbNumFATs * fatSz) + rootDirSectors);
+ uint countOfClusters = dataSec / bpbSecPerClus;
+
+ if (countOfClusters < 4085)
+ {
+ return FatType.Fat12;
+ }
+ else if (countOfClusters < 65525)
+ {
+ return FatType.Fat16;
+ }
+ else
+ {
+ return FatType.Fat32;
+ }
+ }
+
+ private static bool IsRootPath(string path)
+ {
+ return string.IsNullOrEmpty(path) || path == @"\";
+ }
+
+ private static DateTime DefaultTimeConverter(DateTime time, bool toUtc)
+ {
+ return toUtc ? time.ToUniversalTime() : time.ToLocalTime();
+ }
+
+ private void Initialize(Stream data)
+ {
+ _data = data;
+ _data.Position = 0;
+ _bootSector = StreamUtilities.ReadSector(_data);
+
+ _type = DetectFATType(_bootSector);
+
+ ReadBPB();
+
+ LoadFAT();
+
+ LoadClusterReader();
+
+ LoadRootDirectory();
+ }
+
+ private void LoadClusterReader()
+ {
+ int rootDirSectors = ((_bpbRootEntCnt * 32) + (_bpbBytesPerSec - 1)) / _bpbBytesPerSec;
+ int firstDataSector = (int)(_bpbRsvdSecCnt + (_bpbNumFATs * FatSize) + rootDirSectors);
+ _clusterReader = new ClusterReader(_data, firstDataSector, _bpbSecPerClus, _bpbBytesPerSec);
+ }
+
+ private void LoadRootDirectory()
+ {
+ Stream fatStream;
+ if (_type != FatType.Fat32)
+ {
+ fatStream = new SubStream(_data, (_bpbRsvdSecCnt + (_bpbNumFATs * _bpbFATSz16)) * _bpbBytesPerSec, _bpbRootEntCnt * 32);
+ }
+ else
+ {
+ fatStream = new ClusterStream(this, FileAccess.ReadWrite, _bpbRootClus, uint.MaxValue);
+ }
+
+ _rootDir = new Directory(this, fatStream);
+ }
+
+ private void LoadFAT()
+ {
+ _fat = new FileAllocationTable(_type, _data, _bpbRsvdSecCnt, (uint)FatSize, (byte)FatCount, ActiveFat);
+ }
+
+ private void ReadBPB()
+ {
+ _bpbOEMName = Encoding.ASCII.GetString(_bootSector, 3, 8).TrimEnd('\0');
+ _bpbBytesPerSec = EndianUtilities.ToUInt16LittleEndian(_bootSector, 11);
+ _bpbSecPerClus = _bootSector[13];
+ _bpbRsvdSecCnt = EndianUtilities.ToUInt16LittleEndian(_bootSector, 14);
+ _bpbNumFATs = _bootSector[16];
+ _bpbRootEntCnt = EndianUtilities.ToUInt16LittleEndian(_bootSector, 17);
+ _bpbTotSec16 = EndianUtilities.ToUInt16LittleEndian(_bootSector, 19);
+ _bpbMedia = _bootSector[21];
+ _bpbFATSz16 = EndianUtilities.ToUInt16LittleEndian(_bootSector, 22);
+ _bpbSecPerTrk = EndianUtilities.ToUInt16LittleEndian(_bootSector, 24);
+ _bpbNumHeads = EndianUtilities.ToUInt16LittleEndian(_bootSector, 26);
+ _bpbHiddSec = EndianUtilities.ToUInt32LittleEndian(_bootSector, 28);
+ _bpbTotSec32 = EndianUtilities.ToUInt32LittleEndian(_bootSector, 32);
+
+ if (_type != FatType.Fat32)
+ {
+ ReadBS(36);
+ }
+ else
+ {
+ _bpbFATSz32 = EndianUtilities.ToUInt32LittleEndian(_bootSector, 36);
+ _bpbExtFlags = EndianUtilities.ToUInt16LittleEndian(_bootSector, 40);
+ _bpbFSVer = EndianUtilities.ToUInt16LittleEndian(_bootSector, 42);
+ _bpbRootClus = EndianUtilities.ToUInt32LittleEndian(_bootSector, 44);
+ _bpbFSInfo = EndianUtilities.ToUInt16LittleEndian(_bootSector, 48);
+ _bpbBkBootSec = EndianUtilities.ToUInt16LittleEndian(_bootSector, 50);
+ ReadBS(64);
+ }
+ }
+
+ private void ReadBS(int offset)
+ {
+ _bsDrvNum = _bootSector[offset];
+ _bsBootSig = _bootSector[offset + 2];
+ _bsVolId = EndianUtilities.ToUInt32LittleEndian(_bootSector, offset + 3);
+ _bsVolLab = Encoding.ASCII.GetString(_bootSector, offset + 7, 11);
+ _bsFilSysType = Encoding.ASCII.GetString(_bootSector, offset + 18, 8);
+ }
+
+ private long GetDirectoryEntry(Directory dir, string path, out Directory parent)
+ {
+ string[] pathElements = path.Split(new char[] { '\\' }, StringSplitOptions.RemoveEmptyEntries);
+ return GetDirectoryEntry(dir, pathElements, 0, out parent);
+ }
+
+ private long GetDirectoryEntry(Directory dir, string[] pathEntries, int pathOffset, out Directory parent)
+ {
+ long entryId;
+
+ if (pathEntries.Length == 0)
+ {
+ // Looking for root directory, simulate the directory entry in its parent...
+ parent = null;
+ return 0;
+ }
+ else
+ {
+ // Long filename support - translate long filename lookup to short filename
+ string ShortName;
+ if (dir.LongFileNames_LongKey.TryGetValue(pathEntries[pathOffset], out ShortName))
+ pathEntries[pathOffset] = ShortName;
+
+ entryId = dir.FindEntry(new FileName(pathEntries[pathOffset], FatOptions.FileNameEncoding));
+ if (entryId >= 0)
+ {
+ if (pathOffset == pathEntries.Length - 1)
+ {
+ parent = dir;
+ return entryId;
+ }
+ else
+ {
+ return GetDirectoryEntry(GetDirectory(dir, entryId), pathEntries, pathOffset + 1, out parent);
+ }
+ }
+ else if (pathOffset == pathEntries.Length - 1)
+ {
+ parent = dir;
+ return -1;
+ }
+ else
+ {
+ parent = null;
+ return -1;
+ }
+ }
+ }
+
+ private void DoSearch(List results, string path, Regex regex, bool subFolders, bool dirs, bool files)
+ {
+ Directory dir = GetDirectory(path);
+ if (dir == null)
+ {
+ throw new DirectoryNotFoundException(string.Format(CultureInfo.InvariantCulture, "The directory '{0}' was not found", path));
+ }
+
+ DirectoryEntry[] entries = dir.Entries;
+
+ foreach (DirectoryEntry de in entries)
+ {
+ bool isDir = (de.Attributes & FatAttributes.Directory) != 0;
+
+ if ((isDir && dirs) || (!isDir && files))
+ {
+ if (regex.IsMatch(de.Name.GetSearchName(FatOptions.FileNameEncoding)))
+ {
+ results.Add(Utilities.CombinePaths(path, de.Name.GetDisplayName(FatOptions.FileNameEncoding)));
+ }
+ }
+
+ if (subFolders && isDir)
+ {
+ DoSearch(results, Utilities.CombinePaths(path, de.Name.GetDisplayName(FatOptions.FileNameEncoding)), regex, subFolders, dirs, files);
+ }
+ }
+ }
+
+ private void UpdateDirEntry(string path, EntryUpdateAction action)
+ {
+ Directory parent;
+ long id = GetDirectoryEntry(path, out parent);
+ DirectoryEntry entry = parent.GetEntry(id);
+ action(entry);
+ parent.UpdateEntry(id, entry);
+
+ if ((entry.Attributes & FatAttributes.Directory) != 0)
+ {
+ Directory dir = GetDirectory(path);
+ DirectoryEntry selfEntry = dir.SelfEntry;
+ action(selfEntry);
+ dir.SelfEntry = selfEntry;
+ }
+ }
+
+ ///
+ /// Size of the Filesystem in bytes
+ ///
+ public override long Size { get { return ((TotalSectors - ReservedSectorCount - (FatSize * FatCount)) * BytesPerSector); } }
+
+ ///
+ /// Used space of the Filesystem in bytes
+ ///
+ public override long UsedSpace
+ {
+ get
+ {
+ uint usedCluster = 0;
+ for (uint i = 2; i < Fat.NumEntries; i++)
+ {
+ var fatValue = Fat.GetNext(i);
+ if (!Fat.IsFree(fatValue))
+ {
+ usedCluster++;
+ }
+ }
+ return (usedCluster * SectorsPerCluster * BytesPerSector);
+ }
+ }
+
+ ///
+ /// Available space of the Filesystem in bytes
+ ///
+ public override long AvailableSpace { get { return Size - UsedSpace; } }
+
+ ///
+ /// Gets the long name of a given file.
+ ///
+ /// The short full path to the file. Input path segments must be short names.
+ /// The corresponding long file name.
+ public string GetLongFileName(string shortFullPath)
+ {
+ if (shortFullPath == null)
+ throw new ArgumentNullException("shortFullPath");
+
+ string dirPath = Path.GetDirectoryName(shortFullPath);
+ string fileName = Path.GetFileName(shortFullPath);
+ Directory dir = GetDirectory(dirPath);
+ if (dir == null)
+ return fileName;
+
+ string lfn;
+ if (dir.LongFileNames_ShortKey.TryGetValue(Path.GetFileName(shortFullPath), out lfn))
+ return lfn;
+
+ return fileName;
+ }
+
+ ///
+ /// Gets the long path to a given file.
+ ///
+ /// The short full path to the file. Input path segments must be short names.
+ /// The corresponding long file path to the file or null if not found.
+ public string GetLongFilePath(string shortFullPath)
+ {
+ if (shortFullPath == null)
+ throw new ArgumentNullException("shortFullPath");
+
+ string path = null;
+ string current = null;
+ foreach (string segment in shortFullPath.Split(Path.DirectorySeparatorChar))
+ {
+ if (current == null)
+ {
+ current = segment;
+ path = GetLongFileName(current);
+ }
+ else
+ {
+ current = Path.Combine(current, segment);
+ path = Path.Combine(path, GetLongFileName(current));
+ }
+ }
+ return path;
+ }
+ }
+}
diff --git a/DiscUtils/DiscUtils.Fat/FatFileSystemOptions.cs b/DiscUtils/DiscUtils.Fat/FatFileSystemOptions.cs
new file mode 100644
index 0000000..10ec867
--- /dev/null
+++ b/DiscUtils/DiscUtils.Fat/FatFileSystemOptions.cs
@@ -0,0 +1,75 @@
+//
+// Copyright (c) 2008-2011, Kenneth Bell
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the "Software"),
+// to deal in the Software without restriction, including without limitation
+// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+// and/or sell copies of the Software, and to permit persons to whom the
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+//
+
+namespace DiscUtils.Fat
+{
+ using System;
+ using System.Text;
+
+ ///
+ /// FAT file system options.
+ ///
+ public sealed class FatFileSystemOptions : DiscFileSystemOptions
+ {
+ private Encoding _encoding;
+
+ internal FatFileSystemOptions()
+ {
+ Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
+ FileNameEncoding = Encoding.GetEncoding(437);
+ }
+
+ internal FatFileSystemOptions(FileSystemParameters parameters)
+ {
+ if (parameters != null && parameters.FileNameEncoding != null)
+ {
+ FileNameEncoding = parameters.FileNameEncoding;
+ }
+ else
+ {
+ Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
+ FileNameEncoding = Encoding.GetEncoding(437);
+ }
+ }
+
+ ///
+ /// Gets or sets the character encoding used for file names.
+ ///
+ public Encoding FileNameEncoding
+ {
+ get
+ {
+ return _encoding;
+ }
+
+ set
+ {
+ if (!value.IsSingleByte)
+ {
+ throw new ArgumentException(value.EncodingName + " is not a single byte encoding");
+ }
+
+ _encoding = value;
+ }
+ }
+ }
+}
diff --git a/DiscUtils/DiscUtils.Fat/FatType.cs b/DiscUtils/DiscUtils.Fat/FatType.cs
new file mode 100644
index 0000000..c85fb96
--- /dev/null
+++ b/DiscUtils/DiscUtils.Fat/FatType.cs
@@ -0,0 +1,50 @@
+//
+// Copyright (c) 2008-2011, Kenneth Bell
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the "Software"),
+// to deal in the Software without restriction, including without limitation
+// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+// and/or sell copies of the Software, and to permit persons to whom the
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+//
+
+namespace DiscUtils.Fat
+{
+ ///
+ /// Enumeration of known FAT types.
+ ///
+ public enum FatType
+ {
+ ///
+ /// Represents no known FAT type.
+ ///
+ None = 0,
+
+ ///
+ /// Represents a 12-bit FAT.
+ ///
+ Fat12 = 12,
+
+ ///
+ /// Represents a 16-bit FAT.
+ ///
+ Fat16 = 16,
+
+ ///
+ /// Represents a 32-bit FAT.
+ ///
+ Fat32 = 32
+ }
+}
\ No newline at end of file
diff --git a/DiscUtils/DiscUtils.Fat/FileAllocationTable.cs b/DiscUtils/DiscUtils.Fat/FileAllocationTable.cs
new file mode 100644
index 0000000..c034477
--- /dev/null
+++ b/DiscUtils/DiscUtils.Fat/FileAllocationTable.cs
@@ -0,0 +1,106 @@
+//
+// Copyright (c) 2008-2011, Kenneth Bell
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the "Software"),
+// to deal in the Software without restriction, including without limitation
+// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+// and/or sell copies of the Software, and to permit persons to whom the
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+//
+
+using System.IO;
+using DiscUtils.Streams;
+
+namespace DiscUtils.Fat
+{
+ internal class FileAllocationTable
+ {
+ private readonly FatBuffer _buffer;
+ private readonly ushort _firstFatSector;
+ private readonly byte _numFats;
+ private readonly Stream _stream;
+
+ public FileAllocationTable(FatType type, Stream stream, ushort firstFatSector, uint fatSize, byte numFats,
+ byte activeFat)
+ {
+ _stream = stream;
+ _firstFatSector = firstFatSector;
+ _numFats = numFats;
+
+ _stream.Position = (firstFatSector + fatSize * activeFat) * Sizes.Sector;
+ _buffer = new FatBuffer(type, StreamUtilities.ReadExact(_stream, (int)(fatSize * Sizes.Sector)));
+ }
+
+ internal bool IsFree(uint val)
+ {
+ return _buffer.IsFree(val);
+ }
+
+ internal bool IsEndOfChain(uint val)
+ {
+ return _buffer.IsEndOfChain(val);
+ }
+
+ internal bool IsBadCluster(uint val)
+ {
+ return _buffer.IsBadCluster(val);
+ }
+
+ internal uint GetNext(uint cluster)
+ {
+ return _buffer.GetNext(cluster);
+ }
+
+ internal void SetEndOfChain(uint cluster)
+ {
+ _buffer.SetEndOfChain(cluster);
+ }
+
+ internal void SetBadCluster(uint cluster)
+ {
+ _buffer.SetBadCluster(cluster);
+ }
+
+ internal void SetNext(uint cluster, uint next)
+ {
+ _buffer.SetNext(cluster, next);
+ }
+
+ internal void Flush()
+ {
+ for (int i = 0; i < _numFats; ++i)
+ {
+ _buffer.WriteDirtyRegions(_stream, _firstFatSector * Sizes.Sector + _buffer.Size * i);
+ }
+
+ _buffer.ClearDirtyRegions();
+ }
+
+ internal bool TryGetFreeCluster(out uint cluster)
+ {
+ return _buffer.TryGetFreeCluster(out cluster);
+ }
+
+ internal void FreeChain(uint head)
+ {
+ _buffer.FreeChain(head);
+ }
+
+ internal int NumEntries
+ {
+ get { return _buffer.NumEntries; }
+ }
+ }
+}
\ No newline at end of file
diff --git a/DiscUtils/DiscUtils.Fat/FileName.cs b/DiscUtils/DiscUtils.Fat/FileName.cs
new file mode 100644
index 0000000..d4e4767
--- /dev/null
+++ b/DiscUtils/DiscUtils.Fat/FileName.cs
@@ -0,0 +1,213 @@
+//
+// Copyright (c) 2008-2011, Kenneth Bell
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the "Software"),
+// to deal in the Software without restriction, including without limitation
+// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+// and/or sell copies of the Software, and to permit persons to whom the
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+//
+
+namespace DiscUtils.Fat
+{
+ using DiscUtils.Internal;
+ using System;
+ using System.Text;
+
+ internal sealed class FileName : IEquatable
+ {
+ public static readonly FileName SelfEntryName = new FileName(new byte[] { 0x2E, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20 }, 0);
+ public static readonly FileName ParentEntryName = new FileName(new byte[] { 0x2E, 0x2E, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20 }, 0);
+ public static readonly FileName Null = new FileName(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, 0);
+
+ private const byte SpaceByte = 0x20;
+
+ private static readonly byte[] InvalidBytes = new byte[] { 0x22, 0x2A, 0x2B, 0x2C, 0x2E, 0x2F, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x5B, 0x5C, 0x5D, 0x7C };
+
+ private byte[] _raw;
+
+ public FileName(byte[] data, int offset)
+ {
+ _raw = new byte[11];
+ Array.Copy(data, offset, _raw, 0, 11);
+ }
+
+ public FileName(string name, Encoding encoding)
+ {
+ _raw = new byte[11];
+ byte[] bytes = encoding.GetBytes(name.ToUpperInvariant());
+
+ int nameIdx = 0;
+ int rawIdx = 0;
+ while (nameIdx < bytes.Length && bytes[nameIdx] != '.' && rawIdx < _raw.Length)
+ {
+ byte b = bytes[nameIdx++];
+ if (b < 0x20 || Contains(InvalidBytes, b))
+ {
+ throw new ArgumentException("Invalid character in file name '" + (char)b + "'", "name");
+ }
+
+ _raw[rawIdx++] = b;
+ }
+
+ if (rawIdx > 8)
+ {
+ throw new ArgumentException("File name too long '" + name + "'", "name");
+ }
+ else if (rawIdx == 0)
+ {
+ throw new ArgumentException("File name too short '" + name + "'", "name");
+ }
+
+ while (rawIdx < 8)
+ {
+ _raw[rawIdx++] = SpaceByte;
+ }
+
+ if (nameIdx < bytes.Length && bytes[nameIdx] == '.')
+ {
+ ++nameIdx;
+ }
+
+ while (nameIdx < bytes.Length && rawIdx < _raw.Length)
+ {
+ byte b = bytes[nameIdx++];
+ if (b < 0x20 || Contains(InvalidBytes, b))
+ {
+ throw new ArgumentException("Invalid character in file extension '" + (char)b + "'", "name");
+ }
+
+ _raw[rawIdx++] = b;
+ }
+
+ while (rawIdx < 11)
+ {
+ _raw[rawIdx++] = SpaceByte;
+ }
+
+ if (nameIdx != bytes.Length)
+ {
+ throw new ArgumentException("File extension too long '" + name + "'", "name");
+ }
+ }
+
+ public static FileName FromPath(string path, Encoding encoding)
+ {
+ return new FileName(Utilities.GetFileFromPath(path), encoding);
+ }
+
+ public static bool operator ==(FileName a, FileName b)
+ {
+ return CompareRawNames(a, b) == 0;
+ }
+
+ public static bool operator !=(FileName a, FileName b)
+ {
+ return CompareRawNames(a, b) != 0;
+ }
+
+ public string GetDisplayName(Encoding encoding)
+ {
+ return GetSearchName(encoding).TrimEnd('.');
+ }
+
+ public string GetSearchName(Encoding encoding)
+ {
+ return encoding.GetString(_raw, 0, 8).TrimEnd() + "." + encoding.GetString(_raw, 8, 3).TrimEnd();
+ }
+
+ public string GetRawName(Encoding encoding)
+ {
+ return encoding.GetString(_raw, 0, 11).TrimEnd();
+ }
+
+ public FileName Deleted()
+ {
+ byte[] data = new byte[11];
+ Array.Copy(_raw, data, 11);
+ data[0] = 0xE5;
+
+ return new FileName(data, 0);
+ }
+
+ public bool IsDeleted()
+ {
+ return _raw[0] == 0xE5;
+ }
+
+ public bool IsEndMarker()
+ {
+ return _raw[0] == 0x00;
+ }
+
+ public void GetBytes(byte[] data, int offset)
+ {
+ Array.Copy(_raw, 0, data, offset, 11);
+ }
+
+ public override bool Equals(object other)
+ {
+ return Equals(other as FileName);
+ }
+
+ public bool Equals(FileName other)
+ {
+ if (other == null)
+ {
+ return false;
+ }
+
+ return CompareRawNames(this, other) == 0;
+ }
+
+ public override int GetHashCode()
+ {
+ int val = 0x1A8D3C4E;
+
+ for (int i = 0; i < 11; ++i)
+ {
+ val = (val << 2) ^ _raw[i];
+ }
+
+ return val;
+ }
+
+ private static int CompareRawNames(FileName a, FileName b)
+ {
+ for (int i = 0; i < 11; ++i)
+ {
+ if (a._raw[i] != b._raw[i])
+ {
+ return (int)a._raw[i] - (int)b._raw[i];
+ }
+ }
+
+ return 0;
+ }
+
+ private static bool Contains(byte[] array, byte val)
+ {
+ foreach (byte b in array)
+ {
+ if (b == val)
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/DiscUtils/DiscUtils.Fat/FileSystemFactory.cs b/DiscUtils/DiscUtils.Fat/FileSystemFactory.cs
new file mode 100644
index 0000000..25982f7
--- /dev/null
+++ b/DiscUtils/DiscUtils.Fat/FileSystemFactory.cs
@@ -0,0 +1,47 @@
+//
+// Copyright (c) 2008-2011, Kenneth Bell
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the "Software"),
+// to deal in the Software without restriction, including without limitation
+// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+// and/or sell copies of the Software, and to permit persons to whom the
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+//
+
+using System.IO;
+using DiscUtils.Vfs;
+using DiscUtils.Streams;
+
+namespace DiscUtils.Fat
+{
+ [VfsFileSystemFactory]
+ internal class FileSystemFactory : VfsFileSystemFactory
+ {
+ public override FileSystemInfo[] Detect(Stream stream, VolumeInfo volume)
+ {
+ if (FatFileSystem.Detect(stream))
+ {
+ return new FileSystemInfo[] { new VfsFileSystemInfo("FAT", "Microsoft FAT", Open) };
+ }
+
+ return new FileSystemInfo[0];
+ }
+
+ private DiscFileSystem Open(Stream stream, VolumeInfo volumeInfo, FileSystemParameters parameters)
+ {
+ return new FatFileSystem(stream, Ownership.None, parameters);
+ }
+ }
+}
\ No newline at end of file
diff --git a/DiscUtils/DiscUtils.Fat/FirstClusterChangedDelegate.cs b/DiscUtils/DiscUtils.Fat/FirstClusterChangedDelegate.cs
new file mode 100644
index 0000000..a88e652
--- /dev/null
+++ b/DiscUtils/DiscUtils.Fat/FirstClusterChangedDelegate.cs
@@ -0,0 +1,4 @@
+namespace DiscUtils.Fat
+{
+ internal delegate void FirstClusterChangedDelegate(uint cluster);
+}
\ No newline at end of file
diff --git a/Models/FFU.cs b/Models/FFU.cs
index 599ebfa..0531f70 100644
--- a/Models/FFU.cs
+++ b/Models/FFU.cs
@@ -480,7 +480,7 @@ namespace WPinternals
byte[] efiesp = GetPartition("EFIESP");
MemoryStream s = new(efiesp);
DiscUtils.Fat.FatFileSystem fs = new(s);
- Stream mss = fs.OpenFile(@"Windows\System32\Boot\mobilestartup.efi", FileMode.Open, FileAccess.Read);
+ Stream mss = fs.OpenFile(@"\Windows\System32\Boot\mobilestartup.efi", FileMode.Open, FileAccess.Read);
MemoryStream msms = new();
mss.CopyTo(msms);
byte[] mobilestartup = msms.ToArray();
diff --git a/ViewModels/LumiaUnlockBootloaderViewModel.cs b/ViewModels/LumiaUnlockBootloaderViewModel.cs
index 4dfe134..f54ad88 100644
--- a/ViewModels/LumiaUnlockBootloaderViewModel.cs
+++ b/ViewModels/LumiaUnlockBootloaderViewModel.cs
@@ -2753,7 +2753,7 @@ namespace WPinternals
using (DiscUtils.Fat.FatFileSystem SupportedEFIESPFileSystem = new(new MemoryStream(SupportedEFIESP)))
using (DiscUtils.Streams.SparseStream SupportedMobileStartupStream = SupportedEFIESPFileSystem.OpenFile(@"\Windows\System32\Boot\mobilestartup.efi", FileMode.Open))
using (MemoryStream SupportedMobileStartupMemStream = new())
- using (Stream MobileStartupStream = EFIESPFileSystem.OpenFile(@"Windows\System32\Boot\mobilestartup.efi", FileMode.Create, FileAccess.Write))
+ using (Stream MobileStartupStream = EFIESPFileSystem.OpenFile(@"\Windows\System32\Boot\mobilestartup.efi", FileMode.Create, FileAccess.Write))
{
SupportedMobileStartupStream.CopyTo(SupportedMobileStartupMemStream);
byte[] SupportedMobileStartup = SupportedMobileStartupMemStream.ToArray();
@@ -2771,7 +2771,7 @@ namespace WPinternals
}
LogFile.Log("Edit BCD");
- using Stream BCDFileStream = EFIESPFileSystem.OpenFile(@"efi\Microsoft\Boot\BCD", FileMode.Open, FileAccess.ReadWrite);
+ using Stream BCDFileStream = EFIESPFileSystem.OpenFile(@"\efi\Microsoft\Boot\BCD", FileMode.Open, FileAccess.ReadWrite);
using DiscUtils.Registry.RegistryHive BCDHive = new(BCDFileStream);
DiscUtils.BootConfig.Store BCDStore = new(BCDHive.Root);
DiscUtils.BootConfig.BcdObject MobileStartupObject = BCDStore.GetObject(new Guid("{01de5a27-8705-40db-bad6-96fa5187d4a6}"));
diff --git a/WPinternals.csproj b/WPinternals.csproj
index af9823b..3bb0c78 100644
--- a/WPinternals.csproj
+++ b/WPinternals.csproj
@@ -354,7 +354,6 @@
-
@@ -364,6 +363,10 @@
+
+
+
+
"C:\Program Files\PsTools\pskill.exe" XDesProc.exe 2>nul 1>nul