From fa2ec74015595d21bec9cf65d45c034d43202fcf Mon Sep 17 00:00:00 2001 From: Natalia Portillo Date: Sun, 28 Apr 2019 11:57:54 +0100 Subject: [PATCH] Implement support for EAs in FAT32. --- .../DiscImageChef.Filesystems.csproj | 1 + DiscImageChef.Filesystems/FAT/Consts.cs | 39 ++++++-- DiscImageChef.Filesystems/FAT/Dir.cs | 76 ++++++++++------ DiscImageChef.Filesystems/FAT/FAT.cs | 40 ++++----- DiscImageChef.Filesystems/FAT/File.cs | 12 +-- DiscImageChef.Filesystems/FAT/Info.cs | 3 +- DiscImageChef.Filesystems/FAT/Structs.cs | 19 +++- DiscImageChef.Filesystems/FAT/Super.cs | 90 +++++++++++++------ DiscImageChef.Filesystems/FAT/Xattr.cs | 50 +++++++++-- 9 files changed, 238 insertions(+), 92 deletions(-) diff --git a/DiscImageChef.Filesystems/DiscImageChef.Filesystems.csproj b/DiscImageChef.Filesystems/DiscImageChef.Filesystems.csproj index 8449a4131..6aeb9d3fd 100644 --- a/DiscImageChef.Filesystems/DiscImageChef.Filesystems.csproj +++ b/DiscImageChef.Filesystems/DiscImageChef.Filesystems.csproj @@ -19,6 +19,7 @@ $(Version) net461;netstandard2.0 CS0649 + latest $(Version)-{chash:8} built by {mname} in $(Configuration){!:, modified} diff --git a/DiscImageChef.Filesystems/FAT/Consts.cs b/DiscImageChef.Filesystems/FAT/Consts.cs index beae51463..82778db6e 100644 --- a/DiscImageChef.Filesystems/FAT/Consts.cs +++ b/DiscImageChef.Filesystems/FAT/Consts.cs @@ -59,14 +59,6 @@ namespace DiscImageChef.Filesystems.FAT /// Entry points to self or parent directory /// const byte DIRENT_SUBDIR = 0x2E; - /// - /// FASTFAT.SYS indicator that extension is lowercase - /// - const byte FASTFAT_LOWERCASE_EXTENSION = 0x10; - /// - /// FASTFAT.SYS indicator that basename is lowercase - /// - const byte FASTFAT_LOWERCASE_BASENAME = 0x08; const uint FAT32_MASK = 0x0FFFFFFF; const uint FAT32_END_MASK = 0xFFFFFF8; const uint FAT32_FORMATTED = 0xFFFFFF6; @@ -92,6 +84,7 @@ namespace DiscImageChef.Filesystems.FAT const ushort EAT_MVMT = 0xFFDF; const ushort EAT_MVST = 0xFFDE; const ushort EAT_ASN1 = 0xFFDD; + const string FAT32_EA_TAIL = " EA. SF"; readonly (string hash, string name)[] knownBootHashes = { @@ -236,5 +229,35 @@ namespace DiscImageChef.Filesystems.FAT Normal = 0, Critical = 1 } + + [Flags] + enum CaseInfo : byte + { + /// + /// FASTFAT.SYS indicator that basename is lowercase + /// + LowerCaseBasename = 0x08, + /// + /// FASTFAT.SYS indicator that extension is lowercase + /// + LowerCaseExtension = 0x10, + AllLowerCase = 0x18, + /// + /// FAT32.IFS < 0.97 indicator for normal EAs present + /// + NormalEaOld = 0xEA, + /// + /// FAT32.IFS < 0.97 indicator for critical EAs present + /// + CriticalEaOld = 0xEC, + /// + /// FAT32.IFS >= 0.97 indicator for normal EAs present + /// + NormalEa = 0x40, + /// + /// FAT32.IFS >= 0.97 indicator for critical EAs present + /// + CriticalEa = 0x80 + } } } \ No newline at end of file diff --git a/DiscImageChef.Filesystems/FAT/Dir.cs b/DiscImageChef.Filesystems/FAT/Dir.cs index 93d630e7a..d35203e11 100644 --- a/DiscImageChef.Filesystems/FAT/Dir.cs +++ b/DiscImageChef.Filesystems/FAT/Dir.cs @@ -73,7 +73,7 @@ namespace DiscImageChef.Filesystems.FAT ? path.Substring(1).ToLower(cultureInfo) : path.ToLower(cultureInfo); - if(directoryCache.TryGetValue(cutPath, out Dictionary currentDirectory)) + if(directoryCache.TryGetValue(cutPath, out Dictionary currentDirectory)) { contents = currentDirectory.Keys.ToList(); return Errno.NoError; @@ -81,12 +81,12 @@ namespace DiscImageChef.Filesystems.FAT string[] pieces = cutPath.Split(new[] {'/'}, StringSplitOptions.RemoveEmptyEntries); - KeyValuePair entry = + KeyValuePair entry = rootDirectoryCache.FirstOrDefault(t => t.Key.ToLower(cultureInfo) == pieces[0]); 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]; @@ -98,12 +98,12 @@ namespace DiscImageChef.Filesystems.FAT 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]}"; - 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; @@ -121,7 +121,7 @@ namespace DiscImageChef.Filesystems.FAT Array.Copy(buffer, 0, directoryBuffer, i * bytesPerCluster, bytesPerCluster); } - currentDirectory = new Dictionary(); + currentDirectory = new Dictionary(); byte[] lastLfnName = null; byte lastLfnChecksum = 0; List LFNs = new List(); @@ -136,7 +136,7 @@ namespace DiscImageChef.Filesystems.FAT if(dirent.attributes.HasFlag(FatAttributes.LFN)) { - if(@namespace != Namespace.Lfn) continue; + if(@namespace != Namespace.Lfn && @namespace != Namespace.Ecs) continue; LfnEntry lfnEntry = Marshal.ByteArrayToStructureLittleEndian(directoryBuffer, pos, @@ -180,7 +180,9 @@ namespace DiscImageChef.Filesystems.FAT 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); @@ -188,11 +190,9 @@ namespace DiscImageChef.Filesystems.FAT { filename = StringHandlers.CToString(lastLfnName, Encoding.Unicode, true); - LFNs.Add(filename); - currentDirectory[filename] = dirent; - lastLfnName = null; - lastLfnChecksum = 0; - continue; + completeEntry.Lfn = filename; + lastLfnName = null; + lastLfnChecksum = 0; } } @@ -203,10 +203,10 @@ namespace DiscImageChef.Filesystems.FAT if(@namespace == Namespace.Nt) { - if((dirent.caseinfo & FASTFAT_LOWERCASE_EXTENSION) > 0) + if(dirent.caseinfo.HasFlag(CaseInfo.LowerCaseExtension)) extension = extension.ToLower(CultureInfo.CurrentCulture); - if((dirent.caseinfo & FASTFAT_LOWERCASE_BASENAME) > 0) + if(dirent.caseinfo.HasFlag(CaseInfo.LowerCaseBasename)) name = name.ToLower(CultureInfo.CurrentCulture); } @@ -216,21 +216,19 @@ namespace DiscImageChef.Filesystems.FAT // 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 // when using spaces - currentDirectory[filename] = dirent; + completeEntry.Shortname = filename; + currentDirectory[completeEntry.ToString()] = completeEntry; } // Check OS/2 .LONGNAME if(eaCache != null && (@namespace == Namespace.Os2 || @namespace == Namespace.Ecs)) { - List> filesWithEas = - currentDirectory.Where(t => t.Value.ea_handle != 0).ToList(); + List> filesWithEas = + currentDirectory.Where(t => t.Value.Dirent.ea_handle != 0).ToList(); - foreach(KeyValuePair fileWithEa in filesWithEas) + foreach(KeyValuePair fileWithEa in filesWithEas) { - // This ensures LFN takes preference when eCS is in use - if(LFNs.Contains(fileWithEa.Key)) continue; - - Dictionary eas = GetEas(fileWithEa.Value.ea_handle); + Dictionary eas = GetEas(fileWithEa.Value.Dirent.ea_handle); 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 longname = longname.Replace('/', '\u2215'); + fileWithEa.Value.Longname = longname; currentDirectory.Remove(fileWithEa.Key); - currentDirectory[longname] = fileWithEa.Value; + currentDirectory[fileWithEa.Value.ToString()] = fileWithEa.Value; + } + } + + // Check FAT32.IFS EAs + if(fat32 || debug) + { + List> fat32EaSidecars = + currentDirectory.Where(t => t.Key.EndsWith(FAT32_EA_TAIL, true, cultureInfo)).ToList(); + + foreach(KeyValuePair 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); } } diff --git a/DiscImageChef.Filesystems/FAT/FAT.cs b/DiscImageChef.Filesystems/FAT/FAT.cs index f1271db3a..74dc81092 100644 --- a/DiscImageChef.Filesystems/FAT/FAT.cs +++ b/DiscImageChef.Filesystems/FAT/FAT.cs @@ -44,26 +44,26 @@ namespace DiscImageChef.Filesystems.FAT // X68K uses cdate/adate from direntry for extending filename public partial class FAT : IReadOnlyFilesystem { - uint bytesPerCluster; - byte[] cachedEaData; - CultureInfo cultureInfo; - bool debug; - Dictionary> directoryCache; - DirectoryEntry eaDirEntry; - bool fat12; - bool fat16; - bool fat32; - ushort[] fatEntries; - ulong fatFirstSector; - ulong firstClusterSector; - bool mounted; - Namespace @namespace; - uint reservedSectors; - Dictionary rootDirectoryCache; - uint sectorsPerCluster; - uint sectorsPerFat; - FileSystemInfo statfs; - bool useFirstFat; + uint bytesPerCluster; + byte[] cachedEaData; + CultureInfo cultureInfo; + bool debug; + Dictionary> directoryCache; + DirectoryEntry eaDirEntry; + bool fat12; + bool fat16; + bool fat32; + ushort[] fatEntries; + ulong fatFirstSector; + ulong firstClusterSector; + bool mounted; + Namespace @namespace; + uint reservedSectors; + Dictionary rootDirectoryCache; + uint sectorsPerCluster; + uint sectorsPerFat; + FileSystemInfo statfs; + bool useFirstFat; public FileSystemType XmlFsType { get; private set; } diff --git a/DiscImageChef.Filesystems/FAT/File.cs b/DiscImageChef.Filesystems/FAT/File.cs index 1778b4be3..f85b44aba 100644 --- a/DiscImageChef.Filesystems/FAT/File.cs +++ b/DiscImageChef.Filesystems/FAT/File.cs @@ -122,9 +122,11 @@ namespace DiscImageChef.Filesystems.FAT stat = null; 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; + DirectoryEntry entry = completeEntry.Dirent; + stat = new FileEntryInfo { Attributes = new FileAttributes(), @@ -207,9 +209,9 @@ namespace DiscImageChef.Filesystems.FAT return clusters.ToArray(); } - Errno GetFileEntry(string path, out DirectoryEntry entry) + Errno GetFileEntry(string path, out CompleteDirectoryEntry entry) { - entry = new DirectoryEntry(); + entry = null; string cutPath = path.StartsWith("/") ? path.Substring(1).ToLower(cultureInfo) : path.ToLower(cultureInfo); @@ -223,12 +225,12 @@ namespace DiscImageChef.Filesystems.FAT if(err != Errno.NoError) return err; - Dictionary parent; + Dictionary parent; if(pieces.Length == 1) parent = rootDirectoryCache; else if(!directoryCache.TryGetValue(parentPath, out parent)) return Errno.InvalidArgument; - KeyValuePair dirent = + KeyValuePair dirent = parent.FirstOrDefault(t => t.Key.ToLower(cultureInfo) == pieces[pieces.Length - 1]); if(string.IsNullOrEmpty(dirent.Key)) return Errno.NoSuchFile; diff --git a/DiscImageChef.Filesystems/FAT/Info.cs b/DiscImageChef.Filesystems/FAT/Info.cs index 605353984..35ecfb826 100644 --- a/DiscImageChef.Filesystems/FAT/Info.cs +++ b/DiscImageChef.Filesystems/FAT/Info.cs @@ -791,7 +791,8 @@ namespace DiscImageChef.Filesystems.FAT Array.Copy(entry.extension, 0, fullname, 8, 3); string volname = Encoding.GetString(fullname).Trim(); 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) { diff --git a/DiscImageChef.Filesystems/FAT/Structs.cs b/DiscImageChef.Filesystems/FAT/Structs.cs index 36c66ee45..9b4f1108e 100644 --- a/DiscImageChef.Filesystems/FAT/Structs.cs +++ b/DiscImageChef.Filesystems/FAT/Structs.cs @@ -855,7 +855,7 @@ namespace DiscImageChef.Filesystems.FAT [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] public readonly byte[] extension; public readonly FatAttributes attributes; - public readonly byte caseinfo; + public readonly CaseInfo caseinfo; public readonly byte ctime_ms; public readonly ushort ctime; public readonly ushort cdate; @@ -894,5 +894,22 @@ namespace DiscImageChef.Filesystems.FAT public readonly uint unknown; 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; + } + } } } \ No newline at end of file diff --git a/DiscImageChef.Filesystems/FAT/Super.cs b/DiscImageChef.Filesystems/FAT/Super.cs index 5952abb81..3418a80c2 100644 --- a/DiscImageChef.Filesystems/FAT/Super.cs +++ b/DiscImageChef.Filesystems/FAT/Super.cs @@ -368,7 +368,7 @@ namespace DiscImageChef.Filesystems.FAT else fatEntriesPerSector = imagePlugin.Info.SectorSize * 2 / 3; fatFirstSector = partition.Start + reservedSectors * sectorsPerRealSector; - rootDirectoryCache = new Dictionary(); + rootDirectoryCache = new Dictionary(); byte[] rootDirectory = null; if(!fat32) @@ -401,13 +401,15 @@ namespace DiscImageChef.Filesystems.FAT } 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; - byte[] lastLfnName = null; - byte lastLfnChecksum = 0; - List LFNs = new List(); + byte[] lastLfnName = null; + byte lastLfnChecksum = 0; for(int i = 0; i < rootDirectory.Length; i += Marshal.SizeOf()) { @@ -468,9 +470,10 @@ namespace DiscImageChef.Filesystems.FAT Array.Copy(entry.extension, 0, fullname, 8, 3); string volname = Encoding.GetString(fullname).Trim(); if(!string.IsNullOrEmpty(volname)) - XmlFsType.VolumeName = (entry.caseinfo & 0x18) > 0 && this.@namespace == Namespace.Nt - ? volname.ToLower() - : volname; + XmlFsType.VolumeName = + entry.caseinfo.HasFlag(CaseInfo.AllLowerCase) && this.@namespace == Namespace.Nt + ? volname.ToLower() + : volname; if(entry.ctime > 0 && entry.cdate > 0) { @@ -489,6 +492,8 @@ namespace DiscImageChef.Filesystems.FAT continue; } + CompleteDirectoryEntry completeEntry = new CompleteDirectoryEntry {Dirent = entry}; + if((this.@namespace == Namespace.Lfn || this.@namespace == Namespace.Ecs) && lastLfnName != null) { byte calculatedLfnChecksum = LfnChecksum(entry.filename, entry.extension); @@ -497,11 +502,9 @@ namespace DiscImageChef.Filesystems.FAT { filename = StringHandlers.CToString(lastLfnName, Encoding.Unicode, true); - LFNs.Add(filename); - rootDirectoryCache[filename] = entry; - lastLfnName = null; - lastLfnChecksum = 0; - continue; + completeEntry.Lfn = filename; + lastLfnName = null; + lastLfnChecksum = 0; } } @@ -512,30 +515,32 @@ namespace DiscImageChef.Filesystems.FAT if(this.@namespace == Namespace.Nt) { - if((entry.caseinfo & FASTFAT_LOWERCASE_EXTENSION) > 0) + if(entry.caseinfo.HasFlag(CaseInfo.LowerCaseExtension)) extension = extension.ToLower(CultureInfo.CurrentCulture); - if((entry.caseinfo & FASTFAT_LOWERCASE_BASENAME) > 0) + if(entry.caseinfo.HasFlag(CaseInfo.LowerCaseBasename)) name = name.ToLower(CultureInfo.CurrentCulture); } if(extension != "") filename = name + "." + extension; else filename = name; + completeEntry.Shortname = filename; + if(!fat32 && filename == "EA DATA. SF") { eaDirEntry = entry; lastLfnName = null; lastLfnChecksum = 0; - if(debug) rootDirectoryCache[filename] = entry; + if(debug) rootDirectoryCache[completeEntry.ToString()] = completeEntry; continue; } - rootDirectoryCache[filename] = entry; - lastLfnName = null; - lastLfnChecksum = 0; + rootDirectoryCache[completeEntry.ToString()] = completeEntry; + lastLfnName = null; + lastLfnChecksum = 0; } XmlFsType.VolumeName = XmlFsType.VolumeName?.Trim(); @@ -608,7 +613,7 @@ namespace DiscImageChef.Filesystems.FAT // TODO: Check how this affects international filenames cultureInfo = new CultureInfo("en-US", false); - directoryCache = new Dictionary>(); + directoryCache = new Dictionary>(); // Check it is really an OS/2 EA file if(eaDirEntry.start_cluster != 0) @@ -623,19 +628,17 @@ namespace DiscImageChef.Filesystems.FAT } else eaCache = new Dictionary>(); } + else if(fat32) eaCache = new Dictionary>(); // Check OS/2 .LONGNAME if(eaCache != null && (this.@namespace == Namespace.Os2 || this.@namespace == Namespace.Ecs)) { - List> rootFilesWithEas = - rootDirectoryCache.Where(t => t.Value.ea_handle != 0).ToList(); + List> rootFilesWithEas = + rootDirectoryCache.Where(t => t.Value.Dirent.ea_handle != 0).ToList(); - foreach(KeyValuePair fileWithEa in rootFilesWithEas) + foreach(KeyValuePair fileWithEa in rootFilesWithEas) { - // This ensures LFN takes preference when eCS is in use - if(LFNs.Contains(fileWithEa.Key)) continue; - - Dictionary eas = GetEas(fileWithEa.Value.ea_handle); + Dictionary eas = GetEas(fileWithEa.Value.Dirent.ea_handle); 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 longname = longname.Replace('/', '\u2215'); + fileWithEa.Value.Longname = longname; rootDirectoryCache.Remove(fileWithEa.Key); - rootDirectoryCache[longname] = fileWithEa.Value; + rootDirectoryCache[fileWithEa.Value.ToString()] = fileWithEa.Value; + } + } + + // Check FAT32.IFS EAs + if(fat32 || debug) + { + List> fat32EaSidecars = rootDirectoryCache + .Where(t => + t.Key + .EndsWith(FAT32_EA_TAIL, + true, + cultureInfo)) + .ToList(); + + foreach(KeyValuePair 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); } } diff --git a/DiscImageChef.Filesystems/FAT/Xattr.cs b/DiscImageChef.Filesystems/FAT/Xattr.cs index 033f3cbbd..bd285341a 100644 --- a/DiscImageChef.Filesystems/FAT/Xattr.cs +++ b/DiscImageChef.Filesystems/FAT/Xattr.cs @@ -56,7 +56,7 @@ namespace DiscImageChef.Filesystems.FAT if(!mounted) return Errno.AccessDenied; // 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); @@ -66,15 +66,24 @@ namespace DiscImageChef.Filesystems.FAT 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(); - 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; @@ -113,6 +122,30 @@ namespace DiscImageChef.Filesystems.FAT return Errno.NoError; } + Dictionary 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 GetEas(ushort eaHandle) { int aIndex = eaHandle >> 7; @@ -137,6 +170,13 @@ namespace DiscImageChef.Filesystems.FAT byte[] eaData = new byte[eaLen]; Array.Copy(cachedEaData, (int)(eaCluster * bytesPerCluster) + Marshal.SizeOf(), eaData, 0, eaLen); + return GetEas(eaData); + } + + Dictionary GetEas(byte[] eaData) + { + if(eaData is null || eaData.Length < 4) return null; + Dictionary eas = new Dictionary(); if(debug) eas.Add("com.microsoft.os2.fea", eaData);