diff --git a/Aaru.CommonTypes b/Aaru.CommonTypes
index 83d7e34f1..719613f8e 160000
--- a/Aaru.CommonTypes
+++ b/Aaru.CommonTypes
@@ -1 +1 @@
-Subproject commit 83d7e34f1bc52ac7811e67fa7aaf854be7989a4a
+Subproject commit 719613f8e25fa8e3b06bf9371a22ddddf8041c21
diff --git a/Aaru.Filesystems/AppleDOS/Dir.cs b/Aaru.Filesystems/AppleDOS/Dir.cs
index 4a0a53516..0a4a5a9ec 100644
--- a/Aaru.Filesystems/AppleDOS/Dir.cs
+++ b/Aaru.Filesystems/AppleDOS/Dir.cs
@@ -35,6 +35,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using Aaru.CommonTypes.Enums;
+using Aaru.CommonTypes.Interfaces;
using Aaru.Helpers;
namespace Aaru.Filesystems;
@@ -49,6 +50,39 @@ public sealed partial class AppleDOS
return !_mounted ? ErrorNumber.AccessDenied : ErrorNumber.NotSupported;
}
+ ///
+ public ErrorNumber OpenDir(string path, out IDirNode node)
+ {
+ node = null;
+
+ if(!_mounted)
+ return ErrorNumber.AccessDenied;
+
+ if(!string.IsNullOrEmpty(path) &&
+ string.Compare(path, "/", StringComparison.OrdinalIgnoreCase) != 0)
+ return ErrorNumber.NotSupported;
+
+ List contents = _catalogCache.Keys.ToList();
+
+ if(_debug)
+ {
+ contents.Add("$");
+ contents.Add("$Boot");
+ contents.Add("$Vtoc");
+ }
+
+ contents.Sort();
+
+ node = new AppleDosDirNode
+ {
+ Path = path,
+ _position = 0,
+ _contents = contents.ToArray()
+ };
+
+ return ErrorNumber.NoError;
+ }
+
///
public ErrorNumber ReadDir(string path, out List contents)
{
diff --git a/Aaru.Filesystems/AppleDOS/Structs.cs b/Aaru.Filesystems/AppleDOS/Structs.cs
index 5655dd5b1..f23333ee5 100644
--- a/Aaru.Filesystems/AppleDOS/Structs.cs
+++ b/Aaru.Filesystems/AppleDOS/Structs.cs
@@ -114,4 +114,12 @@ public sealed partial class AppleDOS
///
public long Offset { get; set; }
}
+
+ sealed class AppleDosDirNode : IDirNode
+ {
+ internal string[] _contents;
+ internal int _position;
+ ///
+ public string Path { get; init; }
+ }
}
\ No newline at end of file
diff --git a/Aaru.Filesystems/AppleMFS/Dir.cs b/Aaru.Filesystems/AppleMFS/Dir.cs
index 90b8303c3..74724acad 100644
--- a/Aaru.Filesystems/AppleMFS/Dir.cs
+++ b/Aaru.Filesystems/AppleMFS/Dir.cs
@@ -30,6 +30,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using Aaru.CommonTypes.Enums;
+using Aaru.CommonTypes.Interfaces;
using Aaru.Console;
using Aaru.Helpers;
@@ -38,6 +39,42 @@ namespace Aaru.Filesystems;
// Information from Inside Macintosh Volume II
public sealed partial class AppleMFS
{
+ ///
+ public ErrorNumber OpenDir(string path, out IDirNode node)
+ {
+ node = null;
+
+ if(!_mounted)
+ return ErrorNumber.AccessDenied;
+
+ if(!string.IsNullOrEmpty(path) &&
+ string.Compare(path, "/", StringComparison.OrdinalIgnoreCase) != 0)
+ return ErrorNumber.NotSupported;
+
+ List contents = _idToFilename.Select(kvp => kvp.Value).ToList();
+
+ if(_debug)
+ {
+ contents.Add("$");
+ contents.Add("$Bitmap");
+ contents.Add("$MDB");
+
+ if(_bootBlocks != null)
+ contents.Add("$Boot");
+ }
+
+ contents.Sort();
+
+ node = new AppleMfsDirNode
+ {
+ Path = path,
+ _position = 0,
+ contents = contents.ToArray()
+ };
+
+ return ErrorNumber.NoError;
+ }
+
///
public ErrorNumber ReadDir(string path, out List contents)
{
diff --git a/Aaru.Filesystems/AppleMFS/Structs.cs b/Aaru.Filesystems/AppleMFS/Structs.cs
index 782fa4ec6..3c6b23a21 100644
--- a/Aaru.Filesystems/AppleMFS/Structs.cs
+++ b/Aaru.Filesystems/AppleMFS/Structs.cs
@@ -119,4 +119,13 @@ public sealed partial class AppleMFS
///
public long Offset { get; set; }
}
+
+ sealed class AppleMfsDirNode : IDirNode
+ {
+ internal string[] contents;
+
+ internal int _position;
+ ///
+ public string Path { get; init; }
+ }
}
\ No newline at end of file
diff --git a/Aaru.Filesystems/CPM/Dir.cs b/Aaru.Filesystems/CPM/Dir.cs
index a0e23359d..ddc9af591 100644
--- a/Aaru.Filesystems/CPM/Dir.cs
+++ b/Aaru.Filesystems/CPM/Dir.cs
@@ -34,12 +34,35 @@ using System;
using System.Collections.Generic;
using System.Text;
using Aaru.CommonTypes.Enums;
+using Aaru.CommonTypes.Interfaces;
using Aaru.Helpers;
namespace Aaru.Filesystems;
public sealed partial class CPM
{
+ ///
+ public ErrorNumber OpenDir(string path, out IDirNode node)
+ {
+ node = null;
+
+ if(!_mounted)
+ return ErrorNumber.AccessDenied;
+
+ if(!string.IsNullOrEmpty(path) &&
+ string.Compare(path, "/", StringComparison.OrdinalIgnoreCase) != 0)
+ return ErrorNumber.NotSupported;
+
+ node = new CpmDirNode
+ {
+ Path = path,
+ _position = 0,
+ _contents = _dirList.ToArray()
+ };
+
+ return ErrorNumber.NoError;
+ }
+
///
public ErrorNumber ReadDir(string path, out List contents)
{
diff --git a/Aaru.Filesystems/CPM/Structs.cs b/Aaru.Filesystems/CPM/Structs.cs
index c0f9e339d..109ba4048 100644
--- a/Aaru.Filesystems/CPM/Structs.cs
+++ b/Aaru.Filesystems/CPM/Structs.cs
@@ -378,4 +378,12 @@ public sealed partial class CPM
///
public long Offset { get; set; }
}
+
+ sealed class CpmDirNode : IDirNode
+ {
+ internal string[] _contents;
+ internal int _position;
+ ///
+ public string Path { get; init; }
+ }
}
\ No newline at end of file
diff --git a/Aaru.Filesystems/FAT/Dir.cs b/Aaru.Filesystems/FAT/Dir.cs
index 88ba5bc6b..62dfb916e 100644
--- a/Aaru.Filesystems/FAT/Dir.cs
+++ b/Aaru.Filesystems/FAT/Dir.cs
@@ -32,6 +32,7 @@ using System.Globalization;
using System.Linq;
using System.Text;
using Aaru.CommonTypes.Enums;
+using Aaru.CommonTypes.Interfaces;
using Aaru.Console;
using Aaru.Helpers;
@@ -50,6 +51,367 @@ public sealed partial class FAT
return ErrorNumber.NotSupported;
}
+ ///
+ public ErrorNumber OpenDir(string path, out IDirNode node)
+ {
+ node = null;
+
+ if(!_mounted)
+ return ErrorNumber.AccessDenied;
+
+ if(string.IsNullOrWhiteSpace(path) ||
+ path == "/")
+ {
+ node = new FatDirNode
+ {
+ Path = path,
+ _position = 0,
+ _entries = _rootDirectoryCache.Values.ToArray()
+ };
+
+ return ErrorNumber.NoError;
+ }
+
+ string cutPath = path.StartsWith("/", StringComparison.Ordinal) ? path[1..].ToLower(_cultureInfo)
+ : path.ToLower(_cultureInfo);
+
+ if(_directoryCache.TryGetValue(cutPath, out Dictionary currentDirectory))
+ {
+ node = new FatDirNode
+ {
+ Path = path,
+ _position = 0,
+ _entries = currentDirectory.Values.ToArray()
+ };
+
+ return ErrorNumber.NoError;
+ }
+
+ string[] pieces = cutPath.Split(new[]
+ {
+ '/'
+ }, StringSplitOptions.RemoveEmptyEntries);
+
+ KeyValuePair entry =
+ _rootDirectoryCache.FirstOrDefault(t => t.Key.ToLower(_cultureInfo) == pieces[0]);
+
+ if(string.IsNullOrEmpty(entry.Key))
+ return ErrorNumber.NoSuchFile;
+
+ if(!entry.Value.Dirent.attributes.HasFlag(FatAttributes.Subdirectory))
+ return ErrorNumber.NotDirectory;
+
+ string currentPath = pieces[0];
+
+ currentDirectory = _rootDirectoryCache;
+
+ for(int p = 0; p < pieces.Length; p++)
+ {
+ entry = currentDirectory.FirstOrDefault(t => t.Key.ToLower(_cultureInfo) == pieces[p]);
+
+ if(string.IsNullOrEmpty(entry.Key))
+ return ErrorNumber.NoSuchFile;
+
+ if(!entry.Value.Dirent.attributes.HasFlag(FatAttributes.Subdirectory))
+ return ErrorNumber.NotDirectory;
+
+ currentPath = p == 0 ? pieces[0] : $"{currentPath}/{pieces[p]}";
+ uint currentCluster = entry.Value.Dirent.start_cluster;
+
+ if(_fat32)
+ currentCluster += (uint)(entry.Value.Dirent.ea_handle << 16);
+
+ if(_directoryCache.TryGetValue(currentPath, out currentDirectory))
+ continue;
+
+ // Reserved unallocated directory, seen in Atari ST
+ if(currentCluster == 0)
+ {
+ _directoryCache[currentPath] = new Dictionary();
+
+ node = new FatDirNode
+ {
+ Path = path,
+ _position = 0,
+ _entries = Array.Empty()
+ };
+
+ return ErrorNumber.NoError;
+ }
+
+ uint[] clusters = GetClusters(currentCluster);
+
+ if(clusters is null)
+ return ErrorNumber.InvalidArgument;
+
+ byte[] directoryBuffer = new byte[_bytesPerCluster * clusters.Length];
+
+ for(int i = 0; i < clusters.Length; i++)
+ {
+ ErrorNumber errno = _image.ReadSectors(_firstClusterSector + (clusters[i] * _sectorsPerCluster),
+ _sectorsPerCluster, out byte[] buffer);
+
+ if(errno != ErrorNumber.NoError)
+ return errno;
+
+ Array.Copy(buffer, 0, directoryBuffer, i * _bytesPerCluster, _bytesPerCluster);
+ }
+
+ currentDirectory = new Dictionary();
+ byte[] lastLfnName = null;
+ byte lastLfnChecksum = 0;
+
+ for(int pos = 0; pos < directoryBuffer.Length; pos += Marshal.SizeOf())
+ {
+ DirectoryEntry dirent =
+ Marshal.ByteArrayToStructureLittleEndian(directoryBuffer, pos,
+ Marshal.SizeOf());
+
+ if(dirent.filename[0] == DIRENT_FINISHED)
+ break;
+
+ if(dirent.attributes.HasFlag(FatAttributes.LFN))
+ {
+ if(_namespace != Namespace.Lfn &&
+ _namespace != Namespace.Ecs)
+ continue;
+
+ LfnEntry lfnEntry =
+ Marshal.ByteArrayToStructureLittleEndian(directoryBuffer, pos,
+ Marshal.SizeOf());
+
+ int lfnSequence = lfnEntry.sequence & LFN_MASK;
+
+ if((lfnEntry.sequence & LFN_ERASED) > 0)
+ continue;
+
+ if((lfnEntry.sequence & LFN_LAST) > 0)
+ {
+ lastLfnName = new byte[lfnSequence * 26];
+ lastLfnChecksum = lfnEntry.checksum;
+ }
+
+ if(lastLfnName is null)
+ continue;
+
+ if(lfnEntry.checksum != lastLfnChecksum)
+ continue;
+
+ lfnSequence--;
+
+ Array.Copy(lfnEntry.name1, 0, lastLfnName, lfnSequence * 26, 10);
+ Array.Copy(lfnEntry.name2, 0, lastLfnName, (lfnSequence * 26) + 10, 12);
+ Array.Copy(lfnEntry.name3, 0, lastLfnName, (lfnSequence * 26) + 22, 4);
+
+ continue;
+ }
+
+ // Not a correct entry
+ if(dirent.filename[0] < DIRENT_MIN &&
+ dirent.filename[0] != DIRENT_E5)
+ continue;
+
+ // Self
+ if(_encoding.GetString(dirent.filename).TrimEnd() == ".")
+ continue;
+
+ // Parent
+ if(_encoding.GetString(dirent.filename).TrimEnd() == "..")
+ continue;
+
+ // Deleted
+ if(dirent.filename[0] == DIRENT_DELETED)
+ continue;
+
+ string filename;
+
+ if(dirent.attributes.HasFlag(FatAttributes.VolumeLabel))
+ continue;
+
+ var completeEntry = new CompleteDirectoryEntry
+ {
+ Dirent = dirent
+ };
+
+ if(_namespace is Namespace.Lfn or Namespace.Ecs &&
+ lastLfnName != null)
+ {
+ byte calculatedLfnChecksum = LfnChecksum(dirent.filename, dirent.extension);
+
+ if(calculatedLfnChecksum == lastLfnChecksum)
+ {
+ filename = StringHandlers.CToString(lastLfnName, Encoding.Unicode, true);
+
+ completeEntry.Lfn = filename;
+ lastLfnName = null;
+ lastLfnChecksum = 0;
+ }
+ }
+
+ if(dirent.filename[0] == DIRENT_E5)
+ dirent.filename[0] = DIRENT_DELETED;
+
+ string name = _encoding.GetString(dirent.filename).TrimEnd();
+ string extension = _encoding.GetString(dirent.extension).TrimEnd();
+
+ if(name == "" &&
+ extension == "")
+ {
+ AaruConsole.DebugWriteLine("FAT filesystem", Localization.Found_empty_filename_in_0, path);
+
+ if(!_debug ||
+ dirent is { size: > 0, start_cluster: 0 })
+ continue; // Skip invalid name
+
+ // If debug, add it
+ name = ":{EMPTYNAME}:";
+
+ // Try to create a unique filename with an extension from 000 to 999
+ for(int uniq = 0; uniq < 1000; uniq++)
+ {
+ extension = $"{uniq:D03}";
+
+ if(!currentDirectory.ContainsKey($"{name}.{extension}"))
+ break;
+ }
+
+ // If we couldn't find it, just skip over
+ if(currentDirectory.ContainsKey($"{name}.{extension}"))
+ continue;
+ }
+
+ if(_namespace == Namespace.Nt)
+ {
+ if(dirent.caseinfo.HasFlag(CaseInfo.LowerCaseExtension))
+ extension = extension.ToLower(CultureInfo.CurrentCulture);
+
+ if(dirent.caseinfo.HasFlag(CaseInfo.LowerCaseBasename))
+ name = name.ToLower(CultureInfo.CurrentCulture);
+ }
+
+ if(extension != "")
+ filename = name + "." + extension;
+ else
+ filename = name;
+
+ if(_namespace == Namespace.Human)
+ {
+ HumanDirectoryEntry humanEntry =
+ Marshal.ByteArrayToStructureLittleEndian(directoryBuffer, pos,
+ Marshal.SizeOf());
+
+ completeEntry.HumanDirent = humanEntry;
+
+ name = StringHandlers.CToString(humanEntry.name1, _encoding).TrimEnd();
+ extension = StringHandlers.CToString(humanEntry.extension, _encoding).TrimEnd();
+ string name2 = StringHandlers.CToString(humanEntry.name2, _encoding).TrimEnd();
+
+ if(extension != "")
+ filename = name + name2 + "." + extension;
+ else
+ filename = name + name2;
+
+ completeEntry.HumanName = filename;
+ }
+
+ // Atari ST allows slash AND colon so cannot simply substitute one for the other like in Mac filesystems
+ filename = filename.Replace('/', '\u2215');
+
+ // 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
+ completeEntry.Shortname = filename;
+ currentDirectory[completeEntry.ToString()] = completeEntry;
+ }
+
+ // Check OS/2 .LONGNAME
+ if(_eaCache != null &&
+ _namespace is Namespace.Os2 or Namespace.Ecs &&
+ !_fat32)
+ {
+ List> filesWithEas =
+ currentDirectory.Where(t => t.Value.Dirent.ea_handle != 0).ToList();
+
+ foreach(KeyValuePair fileWithEa in filesWithEas)
+ {
+ Dictionary eas = GetEas(fileWithEa.Value.Dirent.ea_handle);
+
+ if(eas is null)
+ continue;
+
+ if(!eas.TryGetValue("com.microsoft.os2.longname", out byte[] longnameEa))
+ continue;
+
+ if(BitConverter.ToUInt16(longnameEa, 0) != EAT_ASCII)
+ continue;
+
+ ushort longnameSize = BitConverter.ToUInt16(longnameEa, 2);
+
+ if(longnameSize + 4 > longnameEa.Length)
+ continue;
+
+ byte[] longnameBytes = new byte[longnameSize];
+
+ Array.Copy(longnameEa, 4, longnameBytes, 0, longnameSize);
+
+ string longname = StringHandlers.CToString(longnameBytes, _encoding);
+
+ if(string.IsNullOrWhiteSpace(longname))
+ continue;
+
+ // 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[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[..^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);
+ }
+ }
+
+ _directoryCache.Add(currentPath, currentDirectory);
+ }
+
+ if(currentDirectory is null)
+ return ErrorNumber.NoSuchFile;
+
+ node = new FatDirNode
+ {
+ Path = path,
+ _position = 0,
+ _entries = currentDirectory.Values.ToArray()
+ };
+
+ return ErrorNumber.NoError;
+ }
+
///
/// Lists contents from a directory.
/// Directory path.
diff --git a/Aaru.Filesystems/FAT/Structs.cs b/Aaru.Filesystems/FAT/Structs.cs
index 15abef2a0..8deeedca9 100644
--- a/Aaru.Filesystems/FAT/Structs.cs
+++ b/Aaru.Filesystems/FAT/Structs.cs
@@ -987,13 +987,20 @@ public sealed partial class FAT
sealed class FatFileNode : IFileNode
{
+ internal uint[] _clusters;
///
public string Path { get; init; }
///
public long Length { get; init; }
///
public long Offset { get; set; }
+ }
- internal uint[] _clusters;
+ sealed class FatDirNode : IDirNode
+ {
+ internal CompleteDirectoryEntry[] _entries;
+ internal int _position;
+ ///
+ public string Path { get; init; }
}
}
\ No newline at end of file
diff --git a/Aaru.Filesystems/FATX/Dir.cs b/Aaru.Filesystems/FATX/Dir.cs
index d9d21499c..2b2838b73 100644
--- a/Aaru.Filesystems/FATX/Dir.cs
+++ b/Aaru.Filesystems/FATX/Dir.cs
@@ -30,12 +30,142 @@ using System;
using System.Collections.Generic;
using System.Linq;
using Aaru.CommonTypes.Enums;
+using Aaru.CommonTypes.Interfaces;
using Aaru.Helpers;
namespace Aaru.Filesystems;
public sealed partial class XboxFatPlugin
{
+ ///
+ public ErrorNumber OpenDir(string path, out IDirNode node)
+ {
+ node = null;
+
+ if(!_mounted)
+ return ErrorNumber.AccessDenied;
+
+ if(string.IsNullOrWhiteSpace(path) ||
+ path == "/")
+ {
+ node = new FatxDirNode
+ {
+ Path = path,
+ _position = 0,
+ _entries = _rootDirectory.Values.ToArray()
+ };
+
+ return ErrorNumber.NoError;
+ }
+
+ string cutPath = path.StartsWith('/') ? path[1..].ToLower(_cultureInfo) : path.ToLower(_cultureInfo);
+
+ if(_directoryCache.TryGetValue(cutPath, out Dictionary currentDirectory))
+ {
+ node = new FatxDirNode
+ {
+ Path = path,
+ _position = 0,
+ _entries = currentDirectory.Values.ToArray()
+ };
+
+ return ErrorNumber.NoError;
+ }
+
+ string[] pieces = cutPath.Split(new[]
+ {
+ '/'
+ }, StringSplitOptions.RemoveEmptyEntries);
+
+ KeyValuePair entry =
+ _rootDirectory.FirstOrDefault(t => t.Key.ToLower(_cultureInfo) == pieces[0]);
+
+ if(string.IsNullOrEmpty(entry.Key))
+ return ErrorNumber.NoSuchFile;
+
+ if(!entry.Value.attributes.HasFlag(Attributes.Directory))
+ return ErrorNumber.NotDirectory;
+
+ string currentPath = pieces[0];
+
+ currentDirectory = _rootDirectory;
+
+ for(int p = 0; p < pieces.Length; p++)
+ {
+ entry = currentDirectory.FirstOrDefault(t => t.Key.ToLower(_cultureInfo) == pieces[p]);
+
+ if(string.IsNullOrEmpty(entry.Key))
+ return ErrorNumber.NoSuchFile;
+
+ if(!entry.Value.attributes.HasFlag(Attributes.Directory))
+ return ErrorNumber.NotDirectory;
+
+ currentPath = p == 0 ? pieces[0] : $"{currentPath}/{pieces[p]}";
+ uint currentCluster = entry.Value.firstCluster;
+
+ if(_directoryCache.TryGetValue(currentPath, out currentDirectory))
+ continue;
+
+ uint[] clusters = GetClusters(currentCluster);
+
+ if(clusters is null)
+ return ErrorNumber.InvalidArgument;
+
+ byte[] directoryBuffer = new byte[_bytesPerCluster * clusters.Length];
+
+ for(int i = 0; i < clusters.Length; i++)
+ {
+ ErrorNumber errno =
+ _imagePlugin.ReadSectors(_firstClusterSector + ((clusters[i] - 1) * _sectorsPerCluster),
+ _sectorsPerCluster, out byte[] buffer);
+
+ if(errno != ErrorNumber.NoError)
+ return errno;
+
+ Array.Copy(buffer, 0, directoryBuffer, i * _bytesPerCluster, _bytesPerCluster);
+ }
+
+ currentDirectory = new Dictionary();
+
+ int pos = 0;
+
+ while(pos < directoryBuffer.Length)
+ {
+ DirectoryEntry dirent = _littleEndian
+ ? Marshal.ByteArrayToStructureLittleEndian(directoryBuffer,
+ pos, Marshal.SizeOf())
+ : Marshal.ByteArrayToStructureBigEndian(directoryBuffer,
+ pos, Marshal.SizeOf());
+
+ pos += Marshal.SizeOf();
+
+ if(dirent.filenameSize is UNUSED_DIRENTRY or FINISHED_DIRENTRY)
+ break;
+
+ if(dirent.filenameSize is DELETED_DIRENTRY or > MAX_FILENAME)
+ continue;
+
+ string filename = _encoding.GetString(dirent.filename, 0, dirent.filenameSize);
+
+ currentDirectory.Add(filename, dirent);
+ }
+
+ _directoryCache.Add(currentPath, currentDirectory);
+ }
+
+ if(currentDirectory is null)
+ return ErrorNumber.NoSuchFile;
+
+ node = new FatxDirNode
+ {
+ Path = path,
+ _position = 0,
+ _entries = currentDirectory.Values.ToArray()
+ };
+
+ return ErrorNumber.NoError;
+ }
+
///
public ErrorNumber ReadDir(string path, out List contents)
{
diff --git a/Aaru.Filesystems/FATX/Structs.cs b/Aaru.Filesystems/FATX/Structs.cs
index cfee20e87..e97d089ca 100644
--- a/Aaru.Filesystems/FATX/Structs.cs
+++ b/Aaru.Filesystems/FATX/Structs.cs
@@ -73,4 +73,12 @@ public sealed partial class XboxFatPlugin
///
public long Offset { get; set; }
}
+
+ sealed class FatxDirNode : IDirNode
+ {
+ internal DirectoryEntry[] _entries;
+ internal int _position;
+ ///
+ public string Path { get; init; }
+ }
}
\ No newline at end of file
diff --git a/Aaru.Filesystems/ISO9660/Dir.cs b/Aaru.Filesystems/ISO9660/Dir.cs
index 26d024225..00c4a7a4e 100644
--- a/Aaru.Filesystems/ISO9660/Dir.cs
+++ b/Aaru.Filesystems/ISO9660/Dir.cs
@@ -34,6 +34,7 @@ using System.IO;
using System.Linq;
using System.Text;
using Aaru.CommonTypes.Enums;
+using Aaru.CommonTypes.Interfaces;
using Aaru.Helpers;
namespace Aaru.Filesystems;
@@ -42,6 +43,112 @@ public sealed partial class ISO9660
{
Dictionary> _directoryCache;
+ ///
+ public ErrorNumber OpenDir(string path, out IDirNode node)
+ {
+ node = null;
+
+ if(!_mounted)
+ return ErrorNumber.AccessDenied;
+
+ if(string.IsNullOrWhiteSpace(path) ||
+ path == "/")
+ {
+ node = new Iso9660DirNode
+ {
+ Path = path,
+ _position = 0,
+ _entries = _rootDirectoryCache.Values.ToArray()
+ };
+
+ return ErrorNumber.NoError;
+ }
+
+ string cutPath = path.StartsWith("/", StringComparison.Ordinal)
+ ? path[1..].ToLower(CultureInfo.CurrentUICulture)
+ : path.ToLower(CultureInfo.CurrentUICulture);
+
+ if(_directoryCache.TryGetValue(cutPath, out Dictionary currentDirectory))
+ {
+ node = new Iso9660DirNode
+ {
+ Path = path,
+ _position = 0,
+ _entries = currentDirectory.Values.ToArray()
+ };
+
+ return ErrorNumber.NoError;
+ }
+
+ string[] pieces = cutPath.Split(new[]
+ {
+ '/'
+ }, StringSplitOptions.RemoveEmptyEntries);
+
+ KeyValuePair entry =
+ _rootDirectoryCache.FirstOrDefault(t => t.Key.ToLower(CultureInfo.CurrentUICulture) == pieces[0]);
+
+ if(string.IsNullOrEmpty(entry.Key))
+ return ErrorNumber.NoSuchFile;
+
+ if(!entry.Value.Flags.HasFlag(FileFlags.Directory))
+ return ErrorNumber.NotDirectory;
+
+ string currentPath = pieces[0];
+
+ currentDirectory = _rootDirectoryCache;
+
+ for(int p = 0; p < pieces.Length; p++)
+ {
+ entry = currentDirectory.FirstOrDefault(t => t.Key.ToLower(CultureInfo.CurrentUICulture) == pieces[p]);
+
+ if(string.IsNullOrEmpty(entry.Key))
+ return ErrorNumber.NoSuchFile;
+
+ if(!entry.Value.Flags.HasFlag(FileFlags.Directory))
+ return ErrorNumber.NotDirectory;
+
+ currentPath = p == 0 ? pieces[0] : $"{currentPath}/{pieces[p]}";
+
+ if(_directoryCache.TryGetValue(currentPath, out currentDirectory))
+ continue;
+
+ if(entry.Value.Extents.Count == 0)
+ return ErrorNumber.InvalidArgument;
+
+ currentDirectory = _cdi
+ ? DecodeCdiDirectory(entry.Value.Extents[0].extent + entry.Value.XattrLength,
+ entry.Value.Extents[0].size)
+ : _highSierra
+ ? DecodeHighSierraDirectory(entry.Value.Extents[0].extent + entry.Value.XattrLength,
+ entry.Value.Extents[0].size)
+ : DecodeIsoDirectory(entry.Value.Extents[0].extent + entry.Value.XattrLength,
+ entry.Value.Extents[0].size);
+
+ if(_usePathTable)
+ foreach(DecodedDirectoryEntry subDirectory in _cdi
+ ? GetSubdirsFromCdiPathTable(currentPath)
+ : _highSierra
+ ? GetSubdirsFromHighSierraPathTable(currentPath)
+ : GetSubdirsFromIsoPathTable(currentPath))
+ currentDirectory[subDirectory.Filename] = subDirectory;
+
+ _directoryCache.Add(currentPath, currentDirectory);
+ }
+
+ if(currentDirectory is null)
+ return ErrorNumber.NoSuchFile;
+
+ node = new Iso9660DirNode
+ {
+ Path = path,
+ _position = 0,
+ _entries = currentDirectory.Values.ToArray()
+ };
+
+ return ErrorNumber.NoError;
+ }
+
///
public ErrorNumber ReadDir(string path, out List contents)
{
@@ -983,7 +1090,8 @@ public sealed partial class ISO9660
errno = ReadSingleExtent(ca.offset, ca.ca_length, ca.block, out byte[] caData);
// TODO: Check continuation area definition, this is not a proper fix
- if(errno == ErrorNumber.NoError && caData.Length > 0)
+ if(errno == ErrorNumber.NoError &&
+ caData.Length > 0)
DecodeSystemArea(caData, 0, (int)ca.ca_length, ref entry, out hasResourceFork);
systemAreaOff += ceLength;
diff --git a/Aaru.Filesystems/ISO9660/Structs/Internal.cs b/Aaru.Filesystems/ISO9660/Structs/Internal.cs
index 003119e4c..21a3aff72 100644
--- a/Aaru.Filesystems/ISO9660/Structs/Internal.cs
+++ b/Aaru.Filesystems/ISO9660/Structs/Internal.cs
@@ -114,4 +114,12 @@ public sealed partial class ISO9660
///
public long Offset { get; set; }
}
+
+ sealed class Iso9660DirNode : IDirNode
+ {
+ internal DecodedDirectoryEntry[] _entries;
+ internal int _position;
+ ///
+ public string Path { get; init; }
+ }
}
\ No newline at end of file
diff --git a/Aaru.Filesystems/LisaFS/Dir.cs b/Aaru.Filesystems/LisaFS/Dir.cs
index 5ee672920..6cbd28fad 100644
--- a/Aaru.Filesystems/LisaFS/Dir.cs
+++ b/Aaru.Filesystems/LisaFS/Dir.cs
@@ -34,6 +34,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using Aaru.CommonTypes.Enums;
+using Aaru.CommonTypes.Interfaces;
using Aaru.CommonTypes.Structs;
using Aaru.Decoders;
using Aaru.Helpers;
@@ -51,6 +52,49 @@ public sealed partial class LisaFS
return ErrorNumber.NotSupported;
}
+ ///
+ public ErrorNumber OpenDir(string path, out IDirNode node)
+ {
+ node = null;
+ ErrorNumber error = LookupFileId(path, out short fileId, out bool isDir);
+
+ if(error != ErrorNumber.NoError)
+ return error;
+
+ if(!isDir)
+ return ErrorNumber.NotDirectory;
+
+ /*List catalog;
+ error = ReadCatalog(fileId, out catalog);
+ if(error != ErrorNumber.NoError)
+ return error;*/
+
+ ReadDir(fileId, out List contents);
+
+ // On debug add system files as readable files
+ // Syntax similar to NTFS
+ if(_debug && fileId == DIRID_ROOT)
+ {
+ contents.Add("$MDDF");
+ contents.Add("$Boot");
+ contents.Add("$Loader");
+ contents.Add("$Bitmap");
+ contents.Add("$S-Record");
+ contents.Add("$");
+ }
+
+ contents.Sort();
+
+ node = new LisaDirNode
+ {
+ Path = path,
+ _contents = contents.ToArray(),
+ _position = 0
+ };
+
+ return ErrorNumber.NoError;
+ }
+
///
public ErrorNumber ReadDir(string path, out List contents)
{
diff --git a/Aaru.Filesystems/LisaFS/Structs.cs b/Aaru.Filesystems/LisaFS/Structs.cs
index bc33dbeb2..d3af653dc 100644
--- a/Aaru.Filesystems/LisaFS/Structs.cs
+++ b/Aaru.Filesystems/LisaFS/Structs.cs
@@ -401,13 +401,20 @@ public sealed partial class LisaFS
sealed class LisaFileNode : IFileNode
{
+ internal short _fileId;
///
public string Path { get; init; }
///
public long Length { get; init; }
///
public long Offset { get; set; }
+ }
- internal short _fileId;
+ sealed class LisaDirNode : IDirNode
+ {
+ internal string[] _contents;
+ internal int _position;
+ ///
+ public string Path { get; init; }
}
}
\ No newline at end of file
diff --git a/Aaru.Filesystems/Opera/Dir.cs b/Aaru.Filesystems/Opera/Dir.cs
index 0515117da..f4829b52e 100644
--- a/Aaru.Filesystems/Opera/Dir.cs
+++ b/Aaru.Filesystems/Opera/Dir.cs
@@ -31,12 +31,104 @@ using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Aaru.CommonTypes.Enums;
+using Aaru.CommonTypes.Interfaces;
using Aaru.Helpers;
namespace Aaru.Filesystems;
public sealed partial class OperaFS
{
+ ///
+ public ErrorNumber OpenDir(string path, out IDirNode node)
+ {
+ node = null;
+
+ if(!_mounted)
+ return ErrorNumber.AccessDenied;
+
+ if(string.IsNullOrWhiteSpace(path) ||
+ path == "/")
+ {
+ node = new OperaDirNode
+ {
+ Path = path,
+ _contents = _rootDirectoryCache.Keys.ToArray(),
+ _position = 0
+ };
+
+ return ErrorNumber.NoError;
+ }
+
+ string cutPath = path.StartsWith("/", StringComparison.Ordinal)
+ ? path[1..].ToLower(CultureInfo.CurrentUICulture)
+ : path.ToLower(CultureInfo.CurrentUICulture);
+
+ if(_directoryCache.TryGetValue(cutPath, out Dictionary currentDirectory))
+ {
+ node = new OperaDirNode
+ {
+ Path = path,
+ _contents = currentDirectory.Keys.ToArray(),
+ _position = 0
+ };
+
+ return ErrorNumber.NoError;
+ }
+
+ string[] pieces = cutPath.Split(new[]
+ {
+ '/'
+ }, StringSplitOptions.RemoveEmptyEntries);
+
+ KeyValuePair entry =
+ _rootDirectoryCache.FirstOrDefault(t => t.Key.ToLower(CultureInfo.CurrentUICulture) == pieces[0]);
+
+ if(string.IsNullOrEmpty(entry.Key))
+ return ErrorNumber.NoSuchFile;
+
+ if((entry.Value.Entry.flags & FLAGS_MASK) != (int)FileFlags.Directory)
+ return ErrorNumber.NotDirectory;
+
+ string currentPath = pieces[0];
+
+ currentDirectory = _rootDirectoryCache;
+
+ for(int p = 0; p < pieces.Length; p++)
+ {
+ entry = currentDirectory.FirstOrDefault(t => t.Key.ToLower(CultureInfo.CurrentUICulture) == pieces[p]);
+
+ if(string.IsNullOrEmpty(entry.Key))
+ return ErrorNumber.NoSuchFile;
+
+ if((entry.Value.Entry.flags & FLAGS_MASK) != (int)FileFlags.Directory)
+ return ErrorNumber.NotDirectory;
+
+ currentPath = p == 0 ? pieces[0] : $"{currentPath}/{pieces[p]}";
+
+ if(_directoryCache.TryGetValue(currentPath, out currentDirectory))
+ continue;
+
+ if(entry.Value.Pointers.Length < 1)
+ return ErrorNumber.InvalidArgument;
+
+ currentDirectory = DecodeDirectory((int)entry.Value.Pointers[0]);
+
+ _directoryCache.Add(currentPath, currentDirectory);
+ }
+
+ if(currentDirectory is null)
+ return ErrorNumber.NoSuchFile;
+
+ node = new OperaDirNode
+ {
+ Path = path,
+ _contents = currentDirectory.Keys.ToArray(),
+ _position = 0
+ };
+
+ return ErrorNumber.NoError;
+ }
+
///
public ErrorNumber ReadDir(string path, out List contents)
{
diff --git a/Aaru.Filesystems/Opera/Structs.cs b/Aaru.Filesystems/Opera/Structs.cs
index 3e5309550..e213e6321 100644
--- a/Aaru.Filesystems/Opera/Structs.cs
+++ b/Aaru.Filesystems/Opera/Structs.cs
@@ -124,4 +124,12 @@ public sealed partial class OperaFS
///
public long Offset { get; set; }
}
+
+ sealed class OperaDirNode : IDirNode
+ {
+ internal string[] _contents;
+ internal int _position;
+ ///
+ public string Path { get; init; }
+ }
}
\ No newline at end of file
diff --git a/Aaru.Filesystems/UCSDPascal/Dir.cs b/Aaru.Filesystems/UCSDPascal/Dir.cs
index a596ff0ee..e5b47c703 100644
--- a/Aaru.Filesystems/UCSDPascal/Dir.cs
+++ b/Aaru.Filesystems/UCSDPascal/Dir.cs
@@ -34,6 +34,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using Aaru.CommonTypes.Enums;
+using Aaru.CommonTypes.Interfaces;
using Aaru.Helpers;
namespace Aaru.Filesystems;
@@ -41,6 +42,39 @@ namespace Aaru.Filesystems;
// Information from Call-A.P.P.L.E. Pascal Disk Directory Structure
public sealed partial class PascalPlugin
{
+ ///
+ public ErrorNumber OpenDir(string path, out IDirNode node)
+ {
+ node = null;
+
+ if(!_mounted)
+ return ErrorNumber.AccessDenied;
+
+ if(!string.IsNullOrEmpty(path) &&
+ string.Compare(path, "/", StringComparison.OrdinalIgnoreCase) != 0)
+ return ErrorNumber.NotSupported;
+
+ List contents = _fileEntries.Select(ent => StringHandlers.PascalToString(ent.Filename, _encoding)).
+ ToList();
+
+ if(_debug)
+ {
+ contents.Add("$");
+ contents.Add("$Boot");
+ }
+
+ contents.Sort();
+
+ node = new PascalDirDone
+ {
+ Path = path,
+ _contents = contents.ToArray(),
+ _position = 0
+ };
+
+ return ErrorNumber.NoError;
+ }
+
///
public ErrorNumber ReadDir(string path, out List contents)
{
diff --git a/Aaru.Filesystems/UCSDPascal/Structs.cs b/Aaru.Filesystems/UCSDPascal/Structs.cs
index 16e40e643..73117b12b 100644
--- a/Aaru.Filesystems/UCSDPascal/Structs.cs
+++ b/Aaru.Filesystems/UCSDPascal/Structs.cs
@@ -87,4 +87,12 @@ public sealed partial class PascalPlugin
///
public long Offset { get; set; }
}
+
+ sealed class PascalDirDone : IDirNode
+ {
+ internal string[] _contents;
+ internal int _position;
+ ///
+ public string Path { get; init; }
+ }
}
\ No newline at end of file