mirror of
https://github.com/aaru-dps/Aaru.git
synced 2025-12-16 19:24:25 +00:00
Implement support for EAs in FAT32.
This commit is contained in:
@@ -19,6 +19,7 @@
|
|||||||
<ApplicationVersion>$(Version)</ApplicationVersion>
|
<ApplicationVersion>$(Version)</ApplicationVersion>
|
||||||
<TargetFrameworks>net461;netstandard2.0</TargetFrameworks>
|
<TargetFrameworks>net461;netstandard2.0</TargetFrameworks>
|
||||||
<NoWarn>CS0649</NoWarn>
|
<NoWarn>CS0649</NoWarn>
|
||||||
|
<LangVersion>latest</LangVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<NrtRevisionFormat>$(Version)-{chash:8} built by {mname} in $(Configuration){!:, modified}</NrtRevisionFormat>
|
<NrtRevisionFormat>$(Version)-{chash:8} built by {mname} in $(Configuration){!:, modified}</NrtRevisionFormat>
|
||||||
|
|||||||
@@ -59,14 +59,6 @@ namespace DiscImageChef.Filesystems.FAT
|
|||||||
/// Entry points to self or parent directory
|
/// Entry points to self or parent directory
|
||||||
/// </summary>
|
/// </summary>
|
||||||
const byte DIRENT_SUBDIR = 0x2E;
|
const byte DIRENT_SUBDIR = 0x2E;
|
||||||
/// <summary>
|
|
||||||
/// FASTFAT.SYS indicator that extension is lowercase
|
|
||||||
/// </summary>
|
|
||||||
const byte FASTFAT_LOWERCASE_EXTENSION = 0x10;
|
|
||||||
/// <summary>
|
|
||||||
/// FASTFAT.SYS indicator that basename is lowercase
|
|
||||||
/// </summary>
|
|
||||||
const byte FASTFAT_LOWERCASE_BASENAME = 0x08;
|
|
||||||
const uint FAT32_MASK = 0x0FFFFFFF;
|
const uint FAT32_MASK = 0x0FFFFFFF;
|
||||||
const uint FAT32_END_MASK = 0xFFFFFF8;
|
const uint FAT32_END_MASK = 0xFFFFFF8;
|
||||||
const uint FAT32_FORMATTED = 0xFFFFFF6;
|
const uint FAT32_FORMATTED = 0xFFFFFF6;
|
||||||
@@ -92,6 +84,7 @@ namespace DiscImageChef.Filesystems.FAT
|
|||||||
const ushort EAT_MVMT = 0xFFDF;
|
const ushort EAT_MVMT = 0xFFDF;
|
||||||
const ushort EAT_MVST = 0xFFDE;
|
const ushort EAT_MVST = 0xFFDE;
|
||||||
const ushort EAT_ASN1 = 0xFFDD;
|
const ushort EAT_ASN1 = 0xFFDD;
|
||||||
|
const string FAT32_EA_TAIL = " EA. SF";
|
||||||
|
|
||||||
readonly (string hash, string name)[] knownBootHashes =
|
readonly (string hash, string name)[] knownBootHashes =
|
||||||
{
|
{
|
||||||
@@ -236,5 +229,35 @@ namespace DiscImageChef.Filesystems.FAT
|
|||||||
Normal = 0,
|
Normal = 0,
|
||||||
Critical = 1
|
Critical = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Flags]
|
||||||
|
enum CaseInfo : byte
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// FASTFAT.SYS indicator that basename is lowercase
|
||||||
|
/// </summary>
|
||||||
|
LowerCaseBasename = 0x08,
|
||||||
|
/// <summary>
|
||||||
|
/// FASTFAT.SYS indicator that extension is lowercase
|
||||||
|
/// </summary>
|
||||||
|
LowerCaseExtension = 0x10,
|
||||||
|
AllLowerCase = 0x18,
|
||||||
|
/// <summary>
|
||||||
|
/// FAT32.IFS < 0.97 indicator for normal EAs present
|
||||||
|
/// </summary>
|
||||||
|
NormalEaOld = 0xEA,
|
||||||
|
/// <summary>
|
||||||
|
/// FAT32.IFS < 0.97 indicator for critical EAs present
|
||||||
|
/// </summary>
|
||||||
|
CriticalEaOld = 0xEC,
|
||||||
|
/// <summary>
|
||||||
|
/// FAT32.IFS >= 0.97 indicator for normal EAs present
|
||||||
|
/// </summary>
|
||||||
|
NormalEa = 0x40,
|
||||||
|
/// <summary>
|
||||||
|
/// FAT32.IFS >= 0.97 indicator for critical EAs present
|
||||||
|
/// </summary>
|
||||||
|
CriticalEa = 0x80
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -73,7 +73,7 @@ namespace DiscImageChef.Filesystems.FAT
|
|||||||
? path.Substring(1).ToLower(cultureInfo)
|
? path.Substring(1).ToLower(cultureInfo)
|
||||||
: path.ToLower(cultureInfo);
|
: path.ToLower(cultureInfo);
|
||||||
|
|
||||||
if(directoryCache.TryGetValue(cutPath, out Dictionary<string, DirectoryEntry> currentDirectory))
|
if(directoryCache.TryGetValue(cutPath, out Dictionary<string, CompleteDirectoryEntry> currentDirectory))
|
||||||
{
|
{
|
||||||
contents = currentDirectory.Keys.ToList();
|
contents = currentDirectory.Keys.ToList();
|
||||||
return Errno.NoError;
|
return Errno.NoError;
|
||||||
@@ -81,12 +81,12 @@ namespace DiscImageChef.Filesystems.FAT
|
|||||||
|
|
||||||
string[] pieces = cutPath.Split(new[] {'/'}, StringSplitOptions.RemoveEmptyEntries);
|
string[] pieces = cutPath.Split(new[] {'/'}, StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
|
||||||
KeyValuePair<string, DirectoryEntry> entry =
|
KeyValuePair<string, CompleteDirectoryEntry> entry =
|
||||||
rootDirectoryCache.FirstOrDefault(t => t.Key.ToLower(cultureInfo) == pieces[0]);
|
rootDirectoryCache.FirstOrDefault(t => t.Key.ToLower(cultureInfo) == pieces[0]);
|
||||||
|
|
||||||
if(string.IsNullOrEmpty(entry.Key)) return Errno.NoSuchFile;
|
if(string.IsNullOrEmpty(entry.Key)) return Errno.NoSuchFile;
|
||||||
|
|
||||||
if(!entry.Value.attributes.HasFlag(FatAttributes.Subdirectory)) return Errno.NotDirectory;
|
if(!entry.Value.Dirent.attributes.HasFlag(FatAttributes.Subdirectory)) return Errno.NotDirectory;
|
||||||
|
|
||||||
string currentPath = pieces[0];
|
string currentPath = pieces[0];
|
||||||
|
|
||||||
@@ -98,12 +98,12 @@ namespace DiscImageChef.Filesystems.FAT
|
|||||||
|
|
||||||
if(string.IsNullOrEmpty(entry.Key)) return Errno.NoSuchFile;
|
if(string.IsNullOrEmpty(entry.Key)) return Errno.NoSuchFile;
|
||||||
|
|
||||||
if(!entry.Value.attributes.HasFlag(FatAttributes.Subdirectory)) return Errno.NotDirectory;
|
if(!entry.Value.Dirent.attributes.HasFlag(FatAttributes.Subdirectory)) return Errno.NotDirectory;
|
||||||
|
|
||||||
currentPath = p == 0 ? pieces[0] : $"{currentPath}/{pieces[p]}";
|
currentPath = p == 0 ? pieces[0] : $"{currentPath}/{pieces[p]}";
|
||||||
uint currentCluster = entry.Value.start_cluster;
|
uint currentCluster = entry.Value.Dirent.start_cluster;
|
||||||
|
|
||||||
if(fat32) currentCluster += (uint)(entry.Value.ea_handle << 16);
|
if(fat32) currentCluster += (uint)(entry.Value.Dirent.ea_handle << 16);
|
||||||
|
|
||||||
if(directoryCache.TryGetValue(currentPath, out currentDirectory)) continue;
|
if(directoryCache.TryGetValue(currentPath, out currentDirectory)) continue;
|
||||||
|
|
||||||
@@ -121,7 +121,7 @@ namespace DiscImageChef.Filesystems.FAT
|
|||||||
Array.Copy(buffer, 0, directoryBuffer, i * bytesPerCluster, bytesPerCluster);
|
Array.Copy(buffer, 0, directoryBuffer, i * bytesPerCluster, bytesPerCluster);
|
||||||
}
|
}
|
||||||
|
|
||||||
currentDirectory = new Dictionary<string, DirectoryEntry>();
|
currentDirectory = new Dictionary<string, CompleteDirectoryEntry>();
|
||||||
byte[] lastLfnName = null;
|
byte[] lastLfnName = null;
|
||||||
byte lastLfnChecksum = 0;
|
byte lastLfnChecksum = 0;
|
||||||
List<string> LFNs = new List<string>();
|
List<string> LFNs = new List<string>();
|
||||||
@@ -136,7 +136,7 @@ namespace DiscImageChef.Filesystems.FAT
|
|||||||
|
|
||||||
if(dirent.attributes.HasFlag(FatAttributes.LFN))
|
if(dirent.attributes.HasFlag(FatAttributes.LFN))
|
||||||
{
|
{
|
||||||
if(@namespace != Namespace.Lfn) continue;
|
if(@namespace != Namespace.Lfn && @namespace != Namespace.Ecs) continue;
|
||||||
|
|
||||||
LfnEntry lfnEntry =
|
LfnEntry lfnEntry =
|
||||||
Marshal.ByteArrayToStructureLittleEndian<LfnEntry>(directoryBuffer, pos,
|
Marshal.ByteArrayToStructureLittleEndian<LfnEntry>(directoryBuffer, pos,
|
||||||
@@ -180,7 +180,9 @@ namespace DiscImageChef.Filesystems.FAT
|
|||||||
|
|
||||||
if(dirent.attributes.HasFlag(FatAttributes.VolumeLabel)) continue;
|
if(dirent.attributes.HasFlag(FatAttributes.VolumeLabel)) continue;
|
||||||
|
|
||||||
if(@namespace == Namespace.Lfn && lastLfnName != null)
|
CompleteDirectoryEntry completeEntry = new CompleteDirectoryEntry {Dirent = dirent};
|
||||||
|
|
||||||
|
if((@namespace == Namespace.Lfn || @namespace == Namespace.Ecs) && lastLfnName != null)
|
||||||
{
|
{
|
||||||
byte calculatedLfnChecksum = LfnChecksum(dirent.filename, dirent.extension);
|
byte calculatedLfnChecksum = LfnChecksum(dirent.filename, dirent.extension);
|
||||||
|
|
||||||
@@ -188,11 +190,9 @@ namespace DiscImageChef.Filesystems.FAT
|
|||||||
{
|
{
|
||||||
filename = StringHandlers.CToString(lastLfnName, Encoding.Unicode, true);
|
filename = StringHandlers.CToString(lastLfnName, Encoding.Unicode, true);
|
||||||
|
|
||||||
LFNs.Add(filename);
|
completeEntry.Lfn = filename;
|
||||||
currentDirectory[filename] = dirent;
|
lastLfnName = null;
|
||||||
lastLfnName = null;
|
lastLfnChecksum = 0;
|
||||||
lastLfnChecksum = 0;
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -203,10 +203,10 @@ namespace DiscImageChef.Filesystems.FAT
|
|||||||
|
|
||||||
if(@namespace == Namespace.Nt)
|
if(@namespace == Namespace.Nt)
|
||||||
{
|
{
|
||||||
if((dirent.caseinfo & FASTFAT_LOWERCASE_EXTENSION) > 0)
|
if(dirent.caseinfo.HasFlag(CaseInfo.LowerCaseExtension))
|
||||||
extension = extension.ToLower(CultureInfo.CurrentCulture);
|
extension = extension.ToLower(CultureInfo.CurrentCulture);
|
||||||
|
|
||||||
if((dirent.caseinfo & FASTFAT_LOWERCASE_BASENAME) > 0)
|
if(dirent.caseinfo.HasFlag(CaseInfo.LowerCaseBasename))
|
||||||
name = name.ToLower(CultureInfo.CurrentCulture);
|
name = name.ToLower(CultureInfo.CurrentCulture);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -216,21 +216,19 @@ namespace DiscImageChef.Filesystems.FAT
|
|||||||
// Using array accessor ensures that repeated entries just get substituted.
|
// Using array accessor ensures that repeated entries just get substituted.
|
||||||
// Repeated entries are not allowed but some bad implementations (e.g. FAT32.IFS)allow to create them
|
// Repeated entries are not allowed but some bad implementations (e.g. FAT32.IFS)allow to create them
|
||||||
// when using spaces
|
// when using spaces
|
||||||
currentDirectory[filename] = dirent;
|
completeEntry.Shortname = filename;
|
||||||
|
currentDirectory[completeEntry.ToString()] = completeEntry;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check OS/2 .LONGNAME
|
// Check OS/2 .LONGNAME
|
||||||
if(eaCache != null && (@namespace == Namespace.Os2 || @namespace == Namespace.Ecs))
|
if(eaCache != null && (@namespace == Namespace.Os2 || @namespace == Namespace.Ecs))
|
||||||
{
|
{
|
||||||
List<KeyValuePair<string, DirectoryEntry>> filesWithEas =
|
List<KeyValuePair<string, CompleteDirectoryEntry>> filesWithEas =
|
||||||
currentDirectory.Where(t => t.Value.ea_handle != 0).ToList();
|
currentDirectory.Where(t => t.Value.Dirent.ea_handle != 0).ToList();
|
||||||
|
|
||||||
foreach(KeyValuePair<string, DirectoryEntry> fileWithEa in filesWithEas)
|
foreach(KeyValuePair<string, CompleteDirectoryEntry> fileWithEa in filesWithEas)
|
||||||
{
|
{
|
||||||
// This ensures LFN takes preference when eCS is in use
|
Dictionary<string, byte[]> eas = GetEas(fileWithEa.Value.Dirent.ea_handle);
|
||||||
if(LFNs.Contains(fileWithEa.Key)) continue;
|
|
||||||
|
|
||||||
Dictionary<string, byte[]> eas = GetEas(fileWithEa.Value.ea_handle);
|
|
||||||
|
|
||||||
if(eas is null) continue;
|
if(eas is null) continue;
|
||||||
|
|
||||||
@@ -253,8 +251,36 @@ namespace DiscImageChef.Filesystems.FAT
|
|||||||
// Forward slash is allowed in .LONGNAME, so change it to visually similar division slash
|
// Forward slash is allowed in .LONGNAME, so change it to visually similar division slash
|
||||||
longname = longname.Replace('/', '\u2215');
|
longname = longname.Replace('/', '\u2215');
|
||||||
|
|
||||||
|
fileWithEa.Value.Longname = longname;
|
||||||
currentDirectory.Remove(fileWithEa.Key);
|
currentDirectory.Remove(fileWithEa.Key);
|
||||||
currentDirectory[longname] = fileWithEa.Value;
|
currentDirectory[fileWithEa.Value.ToString()] = fileWithEa.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check FAT32.IFS EAs
|
||||||
|
if(fat32 || debug)
|
||||||
|
{
|
||||||
|
List<KeyValuePair<string, CompleteDirectoryEntry>> fat32EaSidecars =
|
||||||
|
currentDirectory.Where(t => t.Key.EndsWith(FAT32_EA_TAIL, true, cultureInfo)).ToList();
|
||||||
|
|
||||||
|
foreach(KeyValuePair<string, CompleteDirectoryEntry> sidecar in fat32EaSidecars)
|
||||||
|
{
|
||||||
|
// No real file this sidecar accompanies
|
||||||
|
if(!currentDirectory
|
||||||
|
.TryGetValue(sidecar.Key.Substring(0, sidecar.Key.Length - FAT32_EA_TAIL.Length),
|
||||||
|
out CompleteDirectoryEntry fileWithEa)) continue;
|
||||||
|
|
||||||
|
// If not in debug mode we will consider the lack of EA bitflags to mean the EAs are corrupted or not real
|
||||||
|
if(!debug)
|
||||||
|
if(!fileWithEa.Dirent.caseinfo.HasFlag(CaseInfo.NormalEaOld) &&
|
||||||
|
!fileWithEa.Dirent.caseinfo.HasFlag(CaseInfo.CriticalEa) &&
|
||||||
|
!fileWithEa.Dirent.caseinfo.HasFlag(CaseInfo.NormalEa) &&
|
||||||
|
!fileWithEa.Dirent.caseinfo.HasFlag(CaseInfo.CriticalEa))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
fileWithEa.Fat32Ea = sidecar.Value.Dirent;
|
||||||
|
|
||||||
|
if(!debug) currentDirectory.Remove(sidecar.Key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -44,26 +44,26 @@ namespace DiscImageChef.Filesystems.FAT
|
|||||||
// X68K uses cdate/adate from direntry for extending filename
|
// X68K uses cdate/adate from direntry for extending filename
|
||||||
public partial class FAT : IReadOnlyFilesystem
|
public partial class FAT : IReadOnlyFilesystem
|
||||||
{
|
{
|
||||||
uint bytesPerCluster;
|
uint bytesPerCluster;
|
||||||
byte[] cachedEaData;
|
byte[] cachedEaData;
|
||||||
CultureInfo cultureInfo;
|
CultureInfo cultureInfo;
|
||||||
bool debug;
|
bool debug;
|
||||||
Dictionary<string, Dictionary<string, DirectoryEntry>> directoryCache;
|
Dictionary<string, Dictionary<string, CompleteDirectoryEntry>> directoryCache;
|
||||||
DirectoryEntry eaDirEntry;
|
DirectoryEntry eaDirEntry;
|
||||||
bool fat12;
|
bool fat12;
|
||||||
bool fat16;
|
bool fat16;
|
||||||
bool fat32;
|
bool fat32;
|
||||||
ushort[] fatEntries;
|
ushort[] fatEntries;
|
||||||
ulong fatFirstSector;
|
ulong fatFirstSector;
|
||||||
ulong firstClusterSector;
|
ulong firstClusterSector;
|
||||||
bool mounted;
|
bool mounted;
|
||||||
Namespace @namespace;
|
Namespace @namespace;
|
||||||
uint reservedSectors;
|
uint reservedSectors;
|
||||||
Dictionary<string, DirectoryEntry> rootDirectoryCache;
|
Dictionary<string, CompleteDirectoryEntry> rootDirectoryCache;
|
||||||
uint sectorsPerCluster;
|
uint sectorsPerCluster;
|
||||||
uint sectorsPerFat;
|
uint sectorsPerFat;
|
||||||
FileSystemInfo statfs;
|
FileSystemInfo statfs;
|
||||||
bool useFirstFat;
|
bool useFirstFat;
|
||||||
|
|
||||||
public FileSystemType XmlFsType { get; private set; }
|
public FileSystemType XmlFsType { get; private set; }
|
||||||
|
|
||||||
|
|||||||
@@ -122,9 +122,11 @@ namespace DiscImageChef.Filesystems.FAT
|
|||||||
stat = null;
|
stat = null;
|
||||||
if(!mounted) return Errno.AccessDenied;
|
if(!mounted) return Errno.AccessDenied;
|
||||||
|
|
||||||
Errno err = GetFileEntry(path, out DirectoryEntry entry);
|
Errno err = GetFileEntry(path, out CompleteDirectoryEntry completeEntry);
|
||||||
if(err != Errno.NoError) return err;
|
if(err != Errno.NoError) return err;
|
||||||
|
|
||||||
|
DirectoryEntry entry = completeEntry.Dirent;
|
||||||
|
|
||||||
stat = new FileEntryInfo
|
stat = new FileEntryInfo
|
||||||
{
|
{
|
||||||
Attributes = new FileAttributes(),
|
Attributes = new FileAttributes(),
|
||||||
@@ -207,9 +209,9 @@ namespace DiscImageChef.Filesystems.FAT
|
|||||||
return clusters.ToArray();
|
return clusters.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
Errno GetFileEntry(string path, out DirectoryEntry entry)
|
Errno GetFileEntry(string path, out CompleteDirectoryEntry entry)
|
||||||
{
|
{
|
||||||
entry = new DirectoryEntry();
|
entry = null;
|
||||||
|
|
||||||
string cutPath =
|
string cutPath =
|
||||||
path.StartsWith("/") ? path.Substring(1).ToLower(cultureInfo) : path.ToLower(cultureInfo);
|
path.StartsWith("/") ? path.Substring(1).ToLower(cultureInfo) : path.ToLower(cultureInfo);
|
||||||
@@ -223,12 +225,12 @@ namespace DiscImageChef.Filesystems.FAT
|
|||||||
|
|
||||||
if(err != Errno.NoError) return err;
|
if(err != Errno.NoError) return err;
|
||||||
|
|
||||||
Dictionary<string, DirectoryEntry> parent;
|
Dictionary<string, CompleteDirectoryEntry> parent;
|
||||||
|
|
||||||
if(pieces.Length == 1) parent = rootDirectoryCache;
|
if(pieces.Length == 1) parent = rootDirectoryCache;
|
||||||
else if(!directoryCache.TryGetValue(parentPath, out parent)) return Errno.InvalidArgument;
|
else if(!directoryCache.TryGetValue(parentPath, out parent)) return Errno.InvalidArgument;
|
||||||
|
|
||||||
KeyValuePair<string, DirectoryEntry> dirent =
|
KeyValuePair<string, CompleteDirectoryEntry> dirent =
|
||||||
parent.FirstOrDefault(t => t.Key.ToLower(cultureInfo) == pieces[pieces.Length - 1]);
|
parent.FirstOrDefault(t => t.Key.ToLower(cultureInfo) == pieces[pieces.Length - 1]);
|
||||||
|
|
||||||
if(string.IsNullOrEmpty(dirent.Key)) return Errno.NoSuchFile;
|
if(string.IsNullOrEmpty(dirent.Key)) return Errno.NoSuchFile;
|
||||||
|
|||||||
@@ -791,7 +791,8 @@ namespace DiscImageChef.Filesystems.FAT
|
|||||||
Array.Copy(entry.extension, 0, fullname, 8, 3);
|
Array.Copy(entry.extension, 0, fullname, 8, 3);
|
||||||
string volname = Encoding.GetString(fullname).Trim();
|
string volname = Encoding.GetString(fullname).Trim();
|
||||||
if(!string.IsNullOrEmpty(volname))
|
if(!string.IsNullOrEmpty(volname))
|
||||||
XmlFsType.VolumeName = (entry.caseinfo & 0x18) > 0 ? volname.ToLower() : volname;
|
XmlFsType.VolumeName =
|
||||||
|
entry.caseinfo.HasFlag(CaseInfo.AllLowerCase) ? volname.ToLower() : volname;
|
||||||
|
|
||||||
if(entry.ctime > 0 && entry.cdate > 0)
|
if(entry.ctime > 0 && entry.cdate > 0)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -855,7 +855,7 @@ namespace DiscImageChef.Filesystems.FAT
|
|||||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
|
||||||
public readonly byte[] extension;
|
public readonly byte[] extension;
|
||||||
public readonly FatAttributes attributes;
|
public readonly FatAttributes attributes;
|
||||||
public readonly byte caseinfo;
|
public readonly CaseInfo caseinfo;
|
||||||
public readonly byte ctime_ms;
|
public readonly byte ctime_ms;
|
||||||
public readonly ushort ctime;
|
public readonly ushort ctime;
|
||||||
public readonly ushort cdate;
|
public readonly ushort cdate;
|
||||||
@@ -894,5 +894,22 @@ namespace DiscImageChef.Filesystems.FAT
|
|||||||
public readonly uint unknown;
|
public readonly uint unknown;
|
||||||
public readonly ushort zero;
|
public readonly ushort zero;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class CompleteDirectoryEntry
|
||||||
|
{
|
||||||
|
public DirectoryEntry Dirent;
|
||||||
|
public DirectoryEntry Fat32Ea;
|
||||||
|
public string Lfn;
|
||||||
|
public string Longname;
|
||||||
|
public string Shortname;
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
// This ensures LFN takes preference when eCS is in use
|
||||||
|
if(!string.IsNullOrEmpty(Lfn)) return Lfn;
|
||||||
|
|
||||||
|
return !string.IsNullOrEmpty(Longname) ? Longname : Shortname;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -368,7 +368,7 @@ namespace DiscImageChef.Filesystems.FAT
|
|||||||
else fatEntriesPerSector = imagePlugin.Info.SectorSize * 2 / 3;
|
else fatEntriesPerSector = imagePlugin.Info.SectorSize * 2 / 3;
|
||||||
fatFirstSector = partition.Start + reservedSectors * sectorsPerRealSector;
|
fatFirstSector = partition.Start + reservedSectors * sectorsPerRealSector;
|
||||||
|
|
||||||
rootDirectoryCache = new Dictionary<string, DirectoryEntry>();
|
rootDirectoryCache = new Dictionary<string, CompleteDirectoryEntry>();
|
||||||
byte[] rootDirectory = null;
|
byte[] rootDirectory = null;
|
||||||
|
|
||||||
if(!fat32)
|
if(!fat32)
|
||||||
@@ -401,13 +401,15 @@ namespace DiscImageChef.Filesystems.FAT
|
|||||||
}
|
}
|
||||||
|
|
||||||
rootDirectory = rootMs.ToArray();
|
rootDirectory = rootMs.ToArray();
|
||||||
|
|
||||||
|
// OS/2 FAT32.IFS uses LFN instead of .LONGNAME
|
||||||
|
if(this.@namespace == Namespace.Os2) this.@namespace = Namespace.Os2;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(rootDirectory is null) return Errno.InvalidArgument;
|
if(rootDirectory is null) return Errno.InvalidArgument;
|
||||||
|
|
||||||
byte[] lastLfnName = null;
|
byte[] lastLfnName = null;
|
||||||
byte lastLfnChecksum = 0;
|
byte lastLfnChecksum = 0;
|
||||||
List<string> LFNs = new List<string>();
|
|
||||||
|
|
||||||
for(int i = 0; i < rootDirectory.Length; i += Marshal.SizeOf<DirectoryEntry>())
|
for(int i = 0; i < rootDirectory.Length; i += Marshal.SizeOf<DirectoryEntry>())
|
||||||
{
|
{
|
||||||
@@ -468,9 +470,10 @@ namespace DiscImageChef.Filesystems.FAT
|
|||||||
Array.Copy(entry.extension, 0, fullname, 8, 3);
|
Array.Copy(entry.extension, 0, fullname, 8, 3);
|
||||||
string volname = Encoding.GetString(fullname).Trim();
|
string volname = Encoding.GetString(fullname).Trim();
|
||||||
if(!string.IsNullOrEmpty(volname))
|
if(!string.IsNullOrEmpty(volname))
|
||||||
XmlFsType.VolumeName = (entry.caseinfo & 0x18) > 0 && this.@namespace == Namespace.Nt
|
XmlFsType.VolumeName =
|
||||||
? volname.ToLower()
|
entry.caseinfo.HasFlag(CaseInfo.AllLowerCase) && this.@namespace == Namespace.Nt
|
||||||
: volname;
|
? volname.ToLower()
|
||||||
|
: volname;
|
||||||
|
|
||||||
if(entry.ctime > 0 && entry.cdate > 0)
|
if(entry.ctime > 0 && entry.cdate > 0)
|
||||||
{
|
{
|
||||||
@@ -489,6 +492,8 @@ namespace DiscImageChef.Filesystems.FAT
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CompleteDirectoryEntry completeEntry = new CompleteDirectoryEntry {Dirent = entry};
|
||||||
|
|
||||||
if((this.@namespace == Namespace.Lfn || this.@namespace == Namespace.Ecs) && lastLfnName != null)
|
if((this.@namespace == Namespace.Lfn || this.@namespace == Namespace.Ecs) && lastLfnName != null)
|
||||||
{
|
{
|
||||||
byte calculatedLfnChecksum = LfnChecksum(entry.filename, entry.extension);
|
byte calculatedLfnChecksum = LfnChecksum(entry.filename, entry.extension);
|
||||||
@@ -497,11 +502,9 @@ namespace DiscImageChef.Filesystems.FAT
|
|||||||
{
|
{
|
||||||
filename = StringHandlers.CToString(lastLfnName, Encoding.Unicode, true);
|
filename = StringHandlers.CToString(lastLfnName, Encoding.Unicode, true);
|
||||||
|
|
||||||
LFNs.Add(filename);
|
completeEntry.Lfn = filename;
|
||||||
rootDirectoryCache[filename] = entry;
|
lastLfnName = null;
|
||||||
lastLfnName = null;
|
lastLfnChecksum = 0;
|
||||||
lastLfnChecksum = 0;
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -512,30 +515,32 @@ namespace DiscImageChef.Filesystems.FAT
|
|||||||
|
|
||||||
if(this.@namespace == Namespace.Nt)
|
if(this.@namespace == Namespace.Nt)
|
||||||
{
|
{
|
||||||
if((entry.caseinfo & FASTFAT_LOWERCASE_EXTENSION) > 0)
|
if(entry.caseinfo.HasFlag(CaseInfo.LowerCaseExtension))
|
||||||
extension = extension.ToLower(CultureInfo.CurrentCulture);
|
extension = extension.ToLower(CultureInfo.CurrentCulture);
|
||||||
|
|
||||||
if((entry.caseinfo & FASTFAT_LOWERCASE_BASENAME) > 0)
|
if(entry.caseinfo.HasFlag(CaseInfo.LowerCaseBasename))
|
||||||
name = name.ToLower(CultureInfo.CurrentCulture);
|
name = name.ToLower(CultureInfo.CurrentCulture);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(extension != "") filename = name + "." + extension;
|
if(extension != "") filename = name + "." + extension;
|
||||||
else filename = name;
|
else filename = name;
|
||||||
|
|
||||||
|
completeEntry.Shortname = filename;
|
||||||
|
|
||||||
if(!fat32 && filename == "EA DATA. SF")
|
if(!fat32 && filename == "EA DATA. SF")
|
||||||
{
|
{
|
||||||
eaDirEntry = entry;
|
eaDirEntry = entry;
|
||||||
lastLfnName = null;
|
lastLfnName = null;
|
||||||
lastLfnChecksum = 0;
|
lastLfnChecksum = 0;
|
||||||
|
|
||||||
if(debug) rootDirectoryCache[filename] = entry;
|
if(debug) rootDirectoryCache[completeEntry.ToString()] = completeEntry;
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
rootDirectoryCache[filename] = entry;
|
rootDirectoryCache[completeEntry.ToString()] = completeEntry;
|
||||||
lastLfnName = null;
|
lastLfnName = null;
|
||||||
lastLfnChecksum = 0;
|
lastLfnChecksum = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
XmlFsType.VolumeName = XmlFsType.VolumeName?.Trim();
|
XmlFsType.VolumeName = XmlFsType.VolumeName?.Trim();
|
||||||
@@ -608,7 +613,7 @@ namespace DiscImageChef.Filesystems.FAT
|
|||||||
|
|
||||||
// TODO: Check how this affects international filenames
|
// TODO: Check how this affects international filenames
|
||||||
cultureInfo = new CultureInfo("en-US", false);
|
cultureInfo = new CultureInfo("en-US", false);
|
||||||
directoryCache = new Dictionary<string, Dictionary<string, DirectoryEntry>>();
|
directoryCache = new Dictionary<string, Dictionary<string, CompleteDirectoryEntry>>();
|
||||||
|
|
||||||
// Check it is really an OS/2 EA file
|
// Check it is really an OS/2 EA file
|
||||||
if(eaDirEntry.start_cluster != 0)
|
if(eaDirEntry.start_cluster != 0)
|
||||||
@@ -623,19 +628,17 @@ namespace DiscImageChef.Filesystems.FAT
|
|||||||
}
|
}
|
||||||
else eaCache = new Dictionary<string, Dictionary<string, byte[]>>();
|
else eaCache = new Dictionary<string, Dictionary<string, byte[]>>();
|
||||||
}
|
}
|
||||||
|
else if(fat32) eaCache = new Dictionary<string, Dictionary<string, byte[]>>();
|
||||||
|
|
||||||
// Check OS/2 .LONGNAME
|
// Check OS/2 .LONGNAME
|
||||||
if(eaCache != null && (this.@namespace == Namespace.Os2 || this.@namespace == Namespace.Ecs))
|
if(eaCache != null && (this.@namespace == Namespace.Os2 || this.@namespace == Namespace.Ecs))
|
||||||
{
|
{
|
||||||
List<KeyValuePair<string, DirectoryEntry>> rootFilesWithEas =
|
List<KeyValuePair<string, CompleteDirectoryEntry>> rootFilesWithEas =
|
||||||
rootDirectoryCache.Where(t => t.Value.ea_handle != 0).ToList();
|
rootDirectoryCache.Where(t => t.Value.Dirent.ea_handle != 0).ToList();
|
||||||
|
|
||||||
foreach(KeyValuePair<string, DirectoryEntry> fileWithEa in rootFilesWithEas)
|
foreach(KeyValuePair<string, CompleteDirectoryEntry> fileWithEa in rootFilesWithEas)
|
||||||
{
|
{
|
||||||
// This ensures LFN takes preference when eCS is in use
|
Dictionary<string, byte[]> eas = GetEas(fileWithEa.Value.Dirent.ea_handle);
|
||||||
if(LFNs.Contains(fileWithEa.Key)) continue;
|
|
||||||
|
|
||||||
Dictionary<string, byte[]> eas = GetEas(fileWithEa.Value.ea_handle);
|
|
||||||
|
|
||||||
if(eas is null) continue;
|
if(eas is null) continue;
|
||||||
|
|
||||||
@@ -658,8 +661,41 @@ namespace DiscImageChef.Filesystems.FAT
|
|||||||
// Forward slash is allowed in .LONGNAME, so change it to visually similar division slash
|
// Forward slash is allowed in .LONGNAME, so change it to visually similar division slash
|
||||||
longname = longname.Replace('/', '\u2215');
|
longname = longname.Replace('/', '\u2215');
|
||||||
|
|
||||||
|
fileWithEa.Value.Longname = longname;
|
||||||
rootDirectoryCache.Remove(fileWithEa.Key);
|
rootDirectoryCache.Remove(fileWithEa.Key);
|
||||||
rootDirectoryCache[longname] = fileWithEa.Value;
|
rootDirectoryCache[fileWithEa.Value.ToString()] = fileWithEa.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check FAT32.IFS EAs
|
||||||
|
if(fat32 || debug)
|
||||||
|
{
|
||||||
|
List<KeyValuePair<string, CompleteDirectoryEntry>> fat32EaSidecars = rootDirectoryCache
|
||||||
|
.Where(t =>
|
||||||
|
t.Key
|
||||||
|
.EndsWith(FAT32_EA_TAIL,
|
||||||
|
true,
|
||||||
|
cultureInfo))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
foreach(KeyValuePair<string, CompleteDirectoryEntry> sidecar in fat32EaSidecars)
|
||||||
|
{
|
||||||
|
// No real file this sidecar accompanies
|
||||||
|
if(!rootDirectoryCache
|
||||||
|
.TryGetValue(sidecar.Key.Substring(0, sidecar.Key.Length - FAT32_EA_TAIL.Length),
|
||||||
|
out CompleteDirectoryEntry fileWithEa)) continue;
|
||||||
|
|
||||||
|
// If not in debug mode we will consider the lack of EA bitflags to mean the EAs are corrupted or not real
|
||||||
|
if(!debug)
|
||||||
|
if(!fileWithEa.Dirent.caseinfo.HasFlag(CaseInfo.NormalEaOld) &&
|
||||||
|
!fileWithEa.Dirent.caseinfo.HasFlag(CaseInfo.CriticalEa) &&
|
||||||
|
!fileWithEa.Dirent.caseinfo.HasFlag(CaseInfo.NormalEa) &&
|
||||||
|
!fileWithEa.Dirent.caseinfo.HasFlag(CaseInfo.CriticalEa))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
fileWithEa.Fat32Ea = sidecar.Value.Dirent;
|
||||||
|
|
||||||
|
if(!debug) rootDirectoryCache.Remove(sidecar.Key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ namespace DiscImageChef.Filesystems.FAT
|
|||||||
if(!mounted) return Errno.AccessDenied;
|
if(!mounted) return Errno.AccessDenied;
|
||||||
|
|
||||||
// No other xattr recognized yet
|
// No other xattr recognized yet
|
||||||
if(cachedEaData is null) return Errno.NotSupported;
|
if(cachedEaData is null && !fat32) return Errno.NotSupported;
|
||||||
|
|
||||||
if(path[0] == '/') path = path.Substring(1);
|
if(path[0] == '/') path = path.Substring(1);
|
||||||
|
|
||||||
@@ -66,15 +66,24 @@ namespace DiscImageChef.Filesystems.FAT
|
|||||||
return Errno.NoError;
|
return Errno.NoError;
|
||||||
}
|
}
|
||||||
|
|
||||||
Errno err = GetFileEntry(path, out DirectoryEntry entry);
|
Errno err = GetFileEntry(path, out CompleteDirectoryEntry entry);
|
||||||
|
|
||||||
if(err != Errno.NoError) return err;
|
if(err != Errno.NoError || entry is null) return err;
|
||||||
|
|
||||||
xattrs = new List<string>();
|
xattrs = new List<string>();
|
||||||
|
|
||||||
if(entry.ea_handle == 0) return Errno.NoError;
|
if(!fat32)
|
||||||
|
{
|
||||||
|
if(entry.Dirent.ea_handle == 0) return Errno.NoError;
|
||||||
|
|
||||||
eas = GetEas(entry.ea_handle);
|
eas = GetEas(entry.Dirent.ea_handle);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if(entry.Fat32Ea.start_cluster == 0) return Errno.NoError;
|
||||||
|
|
||||||
|
eas = GetEas(entry.Fat32Ea);
|
||||||
|
}
|
||||||
|
|
||||||
if(eas is null) return Errno.NoError;
|
if(eas is null) return Errno.NoError;
|
||||||
|
|
||||||
@@ -113,6 +122,30 @@ namespace DiscImageChef.Filesystems.FAT
|
|||||||
return Errno.NoError;
|
return Errno.NoError;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Dictionary<string, byte[]> GetEas(DirectoryEntry entryFat32Ea)
|
||||||
|
{
|
||||||
|
MemoryStream eaMs = new MemoryStream();
|
||||||
|
uint[] rootDirectoryClusters = GetClusters(entryFat32Ea.start_cluster);
|
||||||
|
|
||||||
|
foreach(uint cluster in rootDirectoryClusters)
|
||||||
|
{
|
||||||
|
byte[] buffer = image.ReadSectors(firstClusterSector + (cluster - 2) * sectorsPerCluster,
|
||||||
|
sectorsPerCluster);
|
||||||
|
|
||||||
|
eaMs.Write(buffer, 0, buffer.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] full = eaMs.ToArray();
|
||||||
|
ushort size = BitConverter.ToUInt16(full, 0);
|
||||||
|
byte[] eas = new byte[size];
|
||||||
|
Array.Copy(full, 0, eas, 0, size);
|
||||||
|
|
||||||
|
full = null;
|
||||||
|
eaMs.Close();
|
||||||
|
|
||||||
|
return GetEas(eas);
|
||||||
|
}
|
||||||
|
|
||||||
Dictionary<string, byte[]> GetEas(ushort eaHandle)
|
Dictionary<string, byte[]> GetEas(ushort eaHandle)
|
||||||
{
|
{
|
||||||
int aIndex = eaHandle >> 7;
|
int aIndex = eaHandle >> 7;
|
||||||
@@ -137,6 +170,13 @@ namespace DiscImageChef.Filesystems.FAT
|
|||||||
byte[] eaData = new byte[eaLen];
|
byte[] eaData = new byte[eaLen];
|
||||||
Array.Copy(cachedEaData, (int)(eaCluster * bytesPerCluster) + Marshal.SizeOf<EaHeader>(), eaData, 0, eaLen);
|
Array.Copy(cachedEaData, (int)(eaCluster * bytesPerCluster) + Marshal.SizeOf<EaHeader>(), eaData, 0, eaLen);
|
||||||
|
|
||||||
|
return GetEas(eaData);
|
||||||
|
}
|
||||||
|
|
||||||
|
Dictionary<string, byte[]> GetEas(byte[] eaData)
|
||||||
|
{
|
||||||
|
if(eaData is null || eaData.Length < 4) return null;
|
||||||
|
|
||||||
Dictionary<string, byte[]> eas = new Dictionary<string, byte[]>();
|
Dictionary<string, byte[]> eas = new Dictionary<string, byte[]>();
|
||||||
|
|
||||||
if(debug) eas.Add("com.microsoft.os2.fea", eaData);
|
if(debug) eas.Add("com.microsoft.os2.fea", eaData);
|
||||||
|
|||||||
Reference in New Issue
Block a user