Implement support for EAs in FAT32.

This commit is contained in:
2019-04-28 11:57:54 +01:00
parent b2c008eb02
commit fa2ec74015
9 changed files with 238 additions and 92 deletions

View File

@@ -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>

View File

@@ -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 &lt; 0.97 indicator for normal EAs present
/// </summary>
NormalEaOld = 0xEA,
/// <summary>
/// FAT32.IFS &lt; 0.97 indicator for critical EAs present
/// </summary>
CriticalEaOld = 0xEC,
/// <summary>
/// FAT32.IFS &gt;= 0.97 indicator for normal EAs present
/// </summary>
NormalEa = 0x40,
/// <summary>
/// FAT32.IFS &gt;= 0.97 indicator for critical EAs present
/// </summary>
CriticalEa = 0x80
}
} }
} }

View File

@@ -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);
} }
} }

View File

@@ -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; }

View File

@@ -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;

View File

@@ -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)
{ {

View File

@@ -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;
}
}
} }
} }

View File

@@ -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);
} }
} }

View File

@@ -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);