using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Text; using DiscImageChef.CommonTypes.Structs; using DiscImageChef.Helpers; namespace DiscImageChef.Filesystems.ISO9660 { public partial class ISO9660 { Dictionary> directoryCache; // TODO: Implement path table traversal public Errno ReadDir(string path, out List contents) { contents = null; if(!mounted) return Errno.AccessDenied; if(string.IsNullOrWhiteSpace(path) || path == "/") { contents = GetFilenames(rootDirectoryCache); return Errno.NoError; } string cutPath = path.StartsWith("/", StringComparison.Ordinal) ? path.Substring(1).ToLower(CultureInfo.CurrentUICulture) : path.ToLower(CultureInfo.CurrentUICulture); if(directoryCache.TryGetValue(cutPath, out Dictionary currentDirectory)) { contents = currentDirectory.Keys.ToList(); return Errno.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 Errno.NoSuchFile; if(!entry.Value.Flags.HasFlag(FileFlags.Directory)) return Errno.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 Errno.NoSuchFile; if(!entry.Value.Flags.HasFlag(FileFlags.Directory)) return Errno.NotDirectory; currentPath = p == 0 ? pieces[0] : $"{currentPath}/{pieces[p]}"; uint currentExtent = entry.Value.Extent; if(directoryCache.TryGetValue(currentPath, out currentDirectory)) continue; if(currentExtent == 0) return Errno.InvalidArgument; // TODO: XA, High Sierra byte[] directoryBuffer = image.ReadSectors(currentExtent, entry.Value.Size / 2048); // TODO: Decode Joliet currentDirectory = cdi ? DecodeCdiDirectory(directoryBuffer) : highSierra ? DecodeHighSierraDirectory(directoryBuffer) : DecodeIsoDirectory(directoryBuffer); directoryCache.Add(currentPath, currentDirectory); } contents = GetFilenames(currentDirectory); return Errno.NoError; } List GetFilenames(Dictionary dirents) { List contents = new List(); foreach(DecodedDirectoryEntry entry in dirents.Values) switch(@namespace) { case Namespace.Normal: contents.Add(entry.Filename.EndsWith(";1", StringComparison.Ordinal) ? entry.Filename.Substring(0, entry.Filename.Length - 2) : entry.Filename); break; case Namespace.Vms: contents.Add(entry.Filename); break; case Namespace.Joliet: // TODO: Implement Joliet break; case Namespace.Rrip: // TODO: Implement RRIP break; break; default: throw new ArgumentOutOfRangeException(); } return contents; } Dictionary DecodeCdiDirectory(byte[] data) => throw new NotImplementedException(); Dictionary DecodeHighSierraDirectory(byte[] data) { Dictionary entries = new Dictionary(); int entryOff = 0; while(entryOff + DirectoryRecordSize < data.Length) { HighSierraDirectoryRecord record = Marshal.ByteArrayToStructureLittleEndian(data, entryOff, Marshal .SizeOf()); if(record.length == 0) break; // Special entries for current and parent directories, skip them if(record.name_len == 1) if(data[entryOff + DirectoryRecordSize] == 0 || data[entryOff + DirectoryRecordSize] == 1) { entryOff += record.length; continue; } DecodedDirectoryEntry entry = new DecodedDirectoryEntry { Extent = record.size == 0 ? 0 : record.extent, Size = record.size, Flags = record.flags, Interleave = record.interleave, VolumeSequenceNumber = record.volume_sequence_number, Filename = Encoding.GetString(data, entryOff + DirectoryRecordSize, record.name_len), Timestamp = DecodeHighSierraDateTime(record.date) }; if(!entries.ContainsKey(entry.Filename)) entries.Add(entry.Filename, entry); entryOff += record.length; } return entries; } // TODO: Implement system area Dictionary DecodeIsoDirectory(byte[] data) { Dictionary entries = new Dictionary(); int entryOff = 0; while(entryOff + DirectoryRecordSize < data.Length) { DirectoryRecord record = Marshal.ByteArrayToStructureLittleEndian(data, entryOff, Marshal.SizeOf()); if(record.length == 0) break; // Special entries for current and parent directories, skip them if(record.name_len == 1) if(data[entryOff + DirectoryRecordSize] == 0 || data[entryOff + DirectoryRecordSize] == 1) { entryOff += record.length; continue; } DecodedDirectoryEntry entry = new DecodedDirectoryEntry { Extent = record.size == 0 ? 0 : record.extent, Size = record.size, Flags = record.flags, Filename = joliet ? Encoding.BigEndianUnicode.GetString(data, entryOff + DirectoryRecordSize, record.name_len) : Encoding.GetString(data, entryOff + DirectoryRecordSize, record.name_len), FileUnitSize = record.file_unit_size, Interleave = record.interleave, VolumeSequenceNumber = record.volume_sequence_number, Timestamp = DecodeIsoDateTime(record.date) }; // TODO: Multi-extent files if(entry.Flags.HasFlag(FileFlags.Associated)) { // TODO: Detect if Apple extensions, as associated files contain the resource fork there if(entries.ContainsKey(entry.Filename)) entries[entry.Filename].AssociatedFile = entry; else entries[entry.Filename] = new DecodedDirectoryEntry { Extent = 0, Size = 0, Flags = record.flags ^ FileFlags.Associated, FileUnitSize = 0, Interleave = 0, VolumeSequenceNumber = record.volume_sequence_number, Filename = joliet ? Encoding.BigEndianUnicode.GetString(data, entryOff + DirectoryRecordSize, record.name_len) : Encoding.GetString(data, entryOff + DirectoryRecordSize, record.name_len), Timestamp = DecodeIsoDateTime(record.date), AssociatedFile = entry }; } else { if(entries.ContainsKey(entry.Filename)) entry.AssociatedFile = entries[entry.Filename].AssociatedFile; entries[entry.Filename] = entry; } entryOff += record.length; } return entries; } } }