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>
|
||||
<TargetFrameworks>net461;netstandard2.0</TargetFrameworks>
|
||||
<NoWarn>CS0649</NoWarn>
|
||||
<LangVersion>latest</LangVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<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
|
||||
/// </summary>
|
||||
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_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
|
||||
{
|
||||
/// <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.ToLower(cultureInfo);
|
||||
|
||||
if(directoryCache.TryGetValue(cutPath, out Dictionary<string, DirectoryEntry> currentDirectory))
|
||||
if(directoryCache.TryGetValue(cutPath, out Dictionary<string, CompleteDirectoryEntry> currentDirectory))
|
||||
{
|
||||
contents = currentDirectory.Keys.ToList();
|
||||
return Errno.NoError;
|
||||
@@ -81,12 +81,12 @@ namespace DiscImageChef.Filesystems.FAT
|
||||
|
||||
string[] pieces = cutPath.Split(new[] {'/'}, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
KeyValuePair<string, DirectoryEntry> entry =
|
||||
KeyValuePair<string, CompleteDirectoryEntry> 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<string, DirectoryEntry>();
|
||||
currentDirectory = new Dictionary<string, CompleteDirectoryEntry>();
|
||||
byte[] lastLfnName = null;
|
||||
byte lastLfnChecksum = 0;
|
||||
List<string> LFNs = new List<string>();
|
||||
@@ -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<LfnEntry>(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;
|
||||
completeEntry.Lfn = filename;
|
||||
lastLfnName = null;
|
||||
lastLfnChecksum = 0;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<KeyValuePair<string, DirectoryEntry>> filesWithEas =
|
||||
currentDirectory.Where(t => t.Value.ea_handle != 0).ToList();
|
||||
List<KeyValuePair<string, CompleteDirectoryEntry>> filesWithEas =
|
||||
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
|
||||
if(LFNs.Contains(fileWithEa.Key)) continue;
|
||||
|
||||
Dictionary<string, byte[]> eas = GetEas(fileWithEa.Value.ea_handle);
|
||||
Dictionary<string, byte[]> 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<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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ namespace DiscImageChef.Filesystems.FAT
|
||||
byte[] cachedEaData;
|
||||
CultureInfo cultureInfo;
|
||||
bool debug;
|
||||
Dictionary<string, Dictionary<string, DirectoryEntry>> directoryCache;
|
||||
Dictionary<string, Dictionary<string, CompleteDirectoryEntry>> directoryCache;
|
||||
DirectoryEntry eaDirEntry;
|
||||
bool fat12;
|
||||
bool fat16;
|
||||
@@ -59,7 +59,7 @@ namespace DiscImageChef.Filesystems.FAT
|
||||
bool mounted;
|
||||
Namespace @namespace;
|
||||
uint reservedSectors;
|
||||
Dictionary<string, DirectoryEntry> rootDirectoryCache;
|
||||
Dictionary<string, CompleteDirectoryEntry> rootDirectoryCache;
|
||||
uint sectorsPerCluster;
|
||||
uint sectorsPerFat;
|
||||
FileSystemInfo statfs;
|
||||
|
||||
@@ -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<string, DirectoryEntry> parent;
|
||||
Dictionary<string, CompleteDirectoryEntry> parent;
|
||||
|
||||
if(pieces.Length == 1) parent = rootDirectoryCache;
|
||||
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]);
|
||||
|
||||
if(string.IsNullOrEmpty(dirent.Key)) return Errno.NoSuchFile;
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -368,7 +368,7 @@ namespace DiscImageChef.Filesystems.FAT
|
||||
else fatEntriesPerSector = imagePlugin.Info.SectorSize * 2 / 3;
|
||||
fatFirstSector = partition.Start + reservedSectors * sectorsPerRealSector;
|
||||
|
||||
rootDirectoryCache = new Dictionary<string, DirectoryEntry>();
|
||||
rootDirectoryCache = new Dictionary<string, CompleteDirectoryEntry>();
|
||||
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<string> LFNs = new List<string>();
|
||||
|
||||
for(int i = 0; i < rootDirectory.Length; i += Marshal.SizeOf<DirectoryEntry>())
|
||||
{
|
||||
@@ -468,7 +470,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 && this.@namespace == Namespace.Nt
|
||||
XmlFsType.VolumeName =
|
||||
entry.caseinfo.HasFlag(CaseInfo.AllLowerCase) && this.@namespace == Namespace.Nt
|
||||
? volname.ToLower()
|
||||
: volname;
|
||||
|
||||
@@ -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;
|
||||
completeEntry.Lfn = filename;
|
||||
lastLfnName = null;
|
||||
lastLfnChecksum = 0;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -512,28 +515,30 @@ 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;
|
||||
rootDirectoryCache[completeEntry.ToString()] = completeEntry;
|
||||
lastLfnName = null;
|
||||
lastLfnChecksum = 0;
|
||||
}
|
||||
@@ -608,7 +613,7 @@ namespace DiscImageChef.Filesystems.FAT
|
||||
|
||||
// TODO: Check how this affects international filenames
|
||||
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
|
||||
if(eaDirEntry.start_cluster != 0)
|
||||
@@ -623,19 +628,17 @@ namespace DiscImageChef.Filesystems.FAT
|
||||
}
|
||||
else eaCache = new Dictionary<string, Dictionary<string, byte[]>>();
|
||||
}
|
||||
else if(fat32) eaCache = new Dictionary<string, Dictionary<string, byte[]>>();
|
||||
|
||||
// Check OS/2 .LONGNAME
|
||||
if(eaCache != null && (this.@namespace == Namespace.Os2 || this.@namespace == Namespace.Ecs))
|
||||
{
|
||||
List<KeyValuePair<string, DirectoryEntry>> rootFilesWithEas =
|
||||
rootDirectoryCache.Where(t => t.Value.ea_handle != 0).ToList();
|
||||
List<KeyValuePair<string, CompleteDirectoryEntry>> rootFilesWithEas =
|
||||
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
|
||||
if(LFNs.Contains(fileWithEa.Key)) continue;
|
||||
|
||||
Dictionary<string, byte[]> eas = GetEas(fileWithEa.Value.ea_handle);
|
||||
Dictionary<string, byte[]> 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<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;
|
||||
|
||||
// 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<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;
|
||||
|
||||
@@ -113,6 +122,30 @@ namespace DiscImageChef.Filesystems.FAT
|
||||
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)
|
||||
{
|
||||
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<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[]>();
|
||||
|
||||
if(debug) eas.Add("com.microsoft.os2.fea", eaData);
|
||||
|
||||
Reference in New Issue
Block a user