mirror of
https://github.com/ReneLergner/WPinternals.git
synced 2026-06-14 03:16:40 +10:00
DiscUtils: Fat: Fix an issue with no long path name support in the upstream library
This commit is contained in:
+4
-4
@@ -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}"));
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts between two arrays.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the elements of the source array.</typeparam>
|
||||
/// <typeparam name="U">The type of the elements of the destination array.</typeparam>
|
||||
/// <param name="source">The source array.</param>
|
||||
/// <param name="func">The function to map from source type to destination type.</param>
|
||||
/// <returns>The resultant array.</returns>
|
||||
public static U[] Map<T, U>(ICollection<T> source, Func<T, U> func)
|
||||
{
|
||||
U[] result = new U[source.Count];
|
||||
int i = 0;
|
||||
|
||||
foreach (T sVal in source)
|
||||
{
|
||||
result[i++] = func(sVal);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts between two arrays.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the elements of the source array.</typeparam>
|
||||
/// <typeparam name="U">The type of the elements of the destination array.</typeparam>
|
||||
/// <param name="source">The source array.</param>
|
||||
/// <param name="func">The function to map from source type to destination type.</param>
|
||||
/// <returns>The resultant array.</returns>
|
||||
public static U[] Map<T, U>(IEnumerable<T> source, Func<T, U> func)
|
||||
{
|
||||
List<U> result = new List<U>();
|
||||
|
||||
foreach (T sVal in source)
|
||||
{
|
||||
result.Add(func(sVal));
|
||||
}
|
||||
|
||||
return result.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filters a collection into a new collection.
|
||||
/// </summary>
|
||||
/// <typeparam name="C">The type of the new collection.</typeparam>
|
||||
/// <typeparam name="T">The type of the collection entries.</typeparam>
|
||||
/// <param name="source">The collection to filter.</param>
|
||||
/// <param name="predicate">The predicate to select which entries are carried over.</param>
|
||||
/// <returns>The new collection, containing all entries where the predicate returns <c>true</c>.</returns>
|
||||
public static C Filter<C, T>(ICollection<T> source, Func<T, bool> predicate) where C : ICollection<T>, new()
|
||||
{
|
||||
C result = new C();
|
||||
foreach (T val in source)
|
||||
{
|
||||
if (predicate(val))
|
||||
{
|
||||
result.Add(val);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if two ranges overlap.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the ordinals.</typeparam>
|
||||
/// <param name="xFirst">The lowest ordinal of the first range (inclusive).</param>
|
||||
/// <param name="xLast">The highest ordinal of the first range (exclusive).</param>
|
||||
/// <param name="yFirst">The lowest ordinal of the second range (inclusive).</param>
|
||||
/// <param name="yLast">The highest ordinal of the second range (exclusive).</param>
|
||||
/// <returns><c>true</c> if the ranges overlap, else <c>false</c>.</returns>
|
||||
public static bool RangesOverlap<T>(T xFirst, T xLast, T yFirst, T yLast) where T : IComparable<T>
|
||||
{
|
||||
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
|
||||
|
||||
/// <summary>
|
||||
/// Extracts the directory part of a path.
|
||||
/// </summary>
|
||||
/// <param name="path">The path to process.</param>
|
||||
/// <returns>The directory part.</returns>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extracts the file part of a path.
|
||||
/// </summary>
|
||||
/// <param name="path">The path to process.</param>
|
||||
/// <returns>The file part of the path.</returns>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Combines two paths.
|
||||
/// </summary>
|
||||
/// <param name="a">The first part of the path.</param>
|
||||
/// <param name="b">The second part of the path.</param>
|
||||
/// <returns>The combined path.</returns>
|
||||
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('\\');
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolves a relative path into an absolute one.
|
||||
/// </summary>
|
||||
/// <param name="basePath">The base path to resolve from.</param>
|
||||
/// <param name="relativePath">The relative path.</param>
|
||||
/// <returns>The absolute path. If no <paramref name="basePath"/> is specified
|
||||
/// then relativePath is returned as-is. If <paramref name="relativePath"/>
|
||||
/// 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).
|
||||
/// </returns>
|
||||
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<string> pathElements =
|
||||
new List<string>(path.Split(new[] { '\\' }, StringSplitOptions.RemoveEmptyEntries));
|
||||
List<string> basePathElements =
|
||||
new List<string>(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
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if a file name matches the 8.3 pattern.
|
||||
/// </summary>
|
||||
/// <param name="name">The name to test.</param>
|
||||
/// <returns><c>true</c> if the name is 8.3, otherwise <c>false</c>.</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a 'standard' wildcard file/path specification into a regular expression.
|
||||
/// </summary>
|
||||
/// <param name="pattern">The wildcard pattern to convert.</param>
|
||||
/// <returns>The resultant regular expression.</returns>
|
||||
/// <remarks>
|
||||
/// The wildcard * (star) matches zero or more characters (including '.'), and ?
|
||||
/// (question mark) matches precisely one character (except '.').
|
||||
/// </remarks>
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Pre-calculated value because of number of uses of this externally.
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<uint> _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<uint>();
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes up to the next cluster boundary, making sure to preserve existing data in the cluster
|
||||
/// that falls outside of the updated range.
|
||||
/// </summary>
|
||||
/// <param name="cluster">The cluster to write to.</param>
|
||||
/// <param name="pos">The file position of the write (within the cluster).</param>
|
||||
/// <param name="buffer">The buffer with the new data.</param>
|
||||
/// <param name="offset">Offset into buffer of the first byte to write.</param>
|
||||
/// <param name="count">The maximum number of bytes to write.</param>
|
||||
/// <returns>The number of bytes written - either count, or the number that fit up to
|
||||
/// the cluster boundary.</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new cluster to the end of the existing chain, by allocating a free cluster.
|
||||
/// </summary>
|
||||
/// <returns>The cluster allocated.</returns>
|
||||
/// <remarks>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.</remarks>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<long, DirectoryEntry> _entries;
|
||||
private List<long> _freeEntries;
|
||||
private long _endOfEntries;
|
||||
|
||||
private DirectoryEntry _selfEntry;
|
||||
private long _selfEntryLocation;
|
||||
private DirectoryEntry _parentEntry;
|
||||
private long _parentEntryLocation;
|
||||
|
||||
internal Dictionary<string, string> LongFileNames_ShortKey = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
internal Dictionary<string, string> LongFileNames_LongKey = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the Directory class. Use this constructor to represent non-root directories.
|
||||
/// </summary>
|
||||
/// <param name="parent">The parent directory.</param>
|
||||
/// <param name="parentId">The identity of the entry representing this directory in the parent.</param>
|
||||
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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the Directory class. Use this constructor to represent the root directory.
|
||||
/// </summary>
|
||||
/// <param name="fileSystem">The file system.</param>
|
||||
/// <param name="dirStream">The stream containing the directory info.</param>
|
||||
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<DirectoryEntry>(_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<DirectoryEntry> dirs = new List<DirectoryEntry>(_entries.Count);
|
||||
foreach (DirectoryEntry dirEntry in _entries.Values)
|
||||
{
|
||||
if ((dirEntry.Attributes & FatAttributes.Directory) != 0)
|
||||
{
|
||||
dirs.Add(dirEntry);
|
||||
}
|
||||
}
|
||||
|
||||
return dirs.ToArray();
|
||||
}
|
||||
|
||||
public DirectoryEntry[] GetFiles()
|
||||
{
|
||||
List<DirectoryEntry> files = new List<DirectoryEntry>(_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<long, DirectoryEntry>();
|
||||
_freeEntries = new List<long>();
|
||||
|
||||
_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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// The End-of-chain marker to WRITE (SetNext). Don't use this value to test for end of chain.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The actual end-of-chain marker bits on disk vary by FAT type, and can end ...F8 through ...FF.
|
||||
/// </remarks>
|
||||
public const uint EndOfChain = 0xFFFFFFFF;
|
||||
|
||||
/// <summary>
|
||||
/// The Bad-Cluster marker to WRITE (SetNext). Don't use this value to test for bad clusters.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The actual bad-cluster marker bits on disk vary by FAT type.
|
||||
/// </remarks>
|
||||
public const uint BadCluster = 0xFFFFFFF7;
|
||||
|
||||
/// <summary>
|
||||
/// The Free-Cluster marker to WRITE (SetNext). Don't use this value to test for free clusters.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The actual free-cluster marker bits on disk vary by FAT type.
|
||||
/// </remarks>
|
||||
public const uint FreeCluster = 0;
|
||||
|
||||
private const uint DirtyRegionSize = 512;
|
||||
private readonly byte[] _buffer;
|
||||
private readonly Dictionary<uint, uint> _dirtySectors;
|
||||
|
||||
private readonly FatType _type;
|
||||
private uint _nextFreeCandidate;
|
||||
|
||||
public FatBuffer(FatType type, byte[] buffer)
|
||||
{
|
||||
_type = type;
|
||||
_buffer = buffer;
|
||||
_dirtySectors = new Dictionary<uint, uint>();
|
||||
}
|
||||
|
||||
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<uint> GetChain(uint head)
|
||||
{
|
||||
List<uint> result = new List<uint>();
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<StreamExtent> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// FAT file system options.
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the character encoding used for file names.
|
||||
/// </summary>
|
||||
public Encoding FileNameEncoding
|
||||
{
|
||||
get
|
||||
{
|
||||
return _encoding;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (!value.IsSingleByte)
|
||||
{
|
||||
throw new ArgumentException(value.EncodingName + " is not a single byte encoding");
|
||||
}
|
||||
|
||||
_encoding = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Enumeration of known FAT types.
|
||||
/// </summary>
|
||||
public enum FatType
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents no known FAT type.
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Represents a 12-bit FAT.
|
||||
/// </summary>
|
||||
Fat12 = 12,
|
||||
|
||||
/// <summary>
|
||||
/// Represents a 16-bit FAT.
|
||||
/// </summary>
|
||||
Fat16 = 16,
|
||||
|
||||
/// <summary>
|
||||
/// Represents a 32-bit FAT.
|
||||
/// </summary>
|
||||
Fat32 = 32
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<FileName>
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
namespace DiscUtils.Fat
|
||||
{
|
||||
internal delegate void FirstClusterChangedDelegate(uint cluster);
|
||||
}
|
||||
+1
-1
@@ -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();
|
||||
|
||||
@@ -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}"));
|
||||
|
||||
+4
-1
@@ -354,7 +354,6 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="DiscUtils.BootConfig" Version="0.16.4" />
|
||||
<PackageReference Include="DiscUtils.Fat" Version="0.16.4" />
|
||||
<PackageReference Include="DiscUtils.Ntfs" Version="0.16.4" />
|
||||
<PackageReference Include="DiscUtils.Streams" Version="0.16.4" />
|
||||
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
|
||||
@@ -364,6 +363,10 @@
|
||||
<PackageReference Include="System.Management" Version="5.0.0" />
|
||||
<PackageReference Include="System.Text.Json" Version="5.0.2" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="DiscUtils\DiscUtils.Core\CoreCompat\" />
|
||||
<Folder Include="DiscUtils\DiscUtils.Core\Internal\" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
<PreBuildEvent>
|
||||
"C:\Program Files\PsTools\pskill.exe" XDesProc.exe 2>nul 1>nul
|
||||
|
||||
Reference in New Issue
Block a user