diff --git a/DiscImageChef.Filesystems/FAT/Consts.cs b/DiscImageChef.Filesystems/FAT/Consts.cs index a21cd0270..2116345f1 100644 --- a/DiscImageChef.Filesystems/FAT/Consts.cs +++ b/DiscImageChef.Filesystems/FAT/Consts.cs @@ -77,6 +77,9 @@ namespace DiscImageChef.Filesystems.FAT const ushort FAT12_END_MASK = 0xFF8; const ushort FAT12_FORMATTED = 0xFF6; const ushort FAT12_BAD = 0xFF7; + const byte LFN_ERASED = 0x80; + const byte LFN_LAST = 0x40; + const byte LFN_MASK = 0x1F; readonly (string hash, string name)[] knownBootHashes = { @@ -209,7 +212,8 @@ namespace DiscImageChef.Filesystems.FAT enum Namespace { Dos, - Nt + Nt, + Lfn } } } \ No newline at end of file diff --git a/DiscImageChef.Filesystems/FAT/Dir.cs b/DiscImageChef.Filesystems/FAT/Dir.cs index c035ce472..970f2a977 100644 --- a/DiscImageChef.Filesystems/FAT/Dir.cs +++ b/DiscImageChef.Filesystems/FAT/Dir.cs @@ -34,6 +34,7 @@ using System; using System.Collections.Generic; using System.Globalization; using System.Linq; +using System.Text; using DiscImageChef.CommonTypes.Structs; using DiscImageChef.Helpers; @@ -118,18 +119,47 @@ namespace DiscImageChef.Filesystems.FAT } currentDirectory = new Dictionary(); + byte[] lastLfnName = null; + byte lastLfnChecksum = 0; - int pos = 0; - while(pos < directoryBuffer.Length) + for(int pos = 0; pos < directoryBuffer.Length; pos += Marshal.SizeOf()) { DirectoryEntry dirent = Marshal.ByteArrayToStructureLittleEndian(directoryBuffer, pos, Marshal.SizeOf()); - pos += Marshal.SizeOf(); - if(dirent.filename[0] == DIRENT_FINISHED) break; + if(dirent.attributes.HasFlag(FatAttributes.LFN)) + { + if(@namespace != Namespace.Lfn) 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; @@ -142,13 +172,25 @@ namespace DiscImageChef.Filesystems.FAT // Deleted if(dirent.filename[0] == DIRENT_DELETED) continue; - // TODO: LFN namespace - if(dirent.attributes.HasFlag(FatAttributes.LFN)) continue; - string filename; if(dirent.attributes.HasFlag(FatAttributes.VolumeLabel)) continue; + if(@namespace == Namespace.Lfn && lastLfnName != null) + { + byte calculatedLfnChecksum = LfnChecksum(dirent.filename, dirent.extension); + + if(calculatedLfnChecksum == lastLfnChecksum) + { + filename = StringHandlers.CToString(lastLfnName, Encoding.Unicode, true); + + currentDirectory[filename] = dirent; + lastLfnName = null; + lastLfnChecksum = 0; + continue; + } + } + if(dirent.filename[0] == DIRENT_E5) dirent.filename[0] = DIRENT_DELETED; string name = Encoding.GetString(dirent.filename).TrimEnd(); diff --git a/DiscImageChef.Filesystems/FAT/FAT.cs b/DiscImageChef.Filesystems/FAT/FAT.cs index 301d1f356..0774b4d44 100644 --- a/DiscImageChef.Filesystems/FAT/FAT.cs +++ b/DiscImageChef.Filesystems/FAT/FAT.cs @@ -76,7 +76,9 @@ namespace DiscImageChef.Filesystems.FAT public Dictionary Namespaces => new Dictionary { - {"dos", "DOS (8.3 all uppercase)"}, {"nt", "Windows NT (8.3 mixed case, default)"} + {"dos", "DOS (8.3 all uppercase)"}, + {"nt", "Windows NT (8.3 mixed case)"}, + {"lfn", "Long file names (default)"} }; static Dictionary GetDefaultOptions() => diff --git a/DiscImageChef.Filesystems/FAT/File.cs b/DiscImageChef.Filesystems/FAT/File.cs index 8e3b1033d..e2a530c79 100644 --- a/DiscImageChef.Filesystems/FAT/File.cs +++ b/DiscImageChef.Filesystems/FAT/File.cs @@ -233,5 +233,15 @@ namespace DiscImageChef.Filesystems.FAT entry = dirent.Value; return Errno.NoError; } + + byte LfnChecksum(byte[] name, byte[] extension) + { + byte sum = 0; + + for(int i = 0; i < 8; i++) sum = (byte)(((sum & 1) << 7) + (sum >> 1) + name[i]); + for(int i = 0; i < 3; i++) sum = (byte)(((sum & 1) << 7) + (sum >> 1) + extension[i]); + + return sum; + } } } \ No newline at end of file diff --git a/DiscImageChef.Filesystems/FAT/Structs.cs b/DiscImageChef.Filesystems/FAT/Structs.cs index b958209cb..8f36bf837 100644 --- a/DiscImageChef.Filesystems/FAT/Structs.cs +++ b/DiscImageChef.Filesystems/FAT/Structs.cs @@ -866,5 +866,21 @@ namespace DiscImageChef.Filesystems.FAT public readonly ushort start_cluster; public readonly uint size; } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct LfnEntry + { + public readonly byte sequence; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)] + public readonly byte[] name1; + public readonly FatAttributes attributes; + public readonly byte type; + public readonly byte checksum; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 12)] + public readonly byte[] name2; + public readonly ushort start_cluster; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public readonly byte[] name3; + } } } \ No newline at end of file diff --git a/DiscImageChef.Filesystems/FAT/Super.cs b/DiscImageChef.Filesystems/FAT/Super.cs index 797f69508..5160c03a1 100644 --- a/DiscImageChef.Filesystems/FAT/Super.cs +++ b/DiscImageChef.Filesystems/FAT/Super.cs @@ -66,7 +66,7 @@ namespace DiscImageChef.Filesystems.FAT if(options.TryGetValue("debug", out string debugString)) bool.TryParse(debugString, out debug); // Default namespace - if(@namespace is null) @namespace = "nt"; + if(@namespace is null) @namespace = "lfn"; switch(@namespace.ToLowerInvariant()) { @@ -76,6 +76,9 @@ namespace DiscImageChef.Filesystems.FAT case "nt": this.@namespace = Namespace.Nt; break; + case "lfn": + this.@namespace = Namespace.Lfn; + break; default: return Errno.InvalidArgument; } @@ -396,6 +399,9 @@ namespace DiscImageChef.Filesystems.FAT if(rootDirectory is null) return Errno.InvalidArgument; + byte[] lastLfnName = null; + byte lastLfnChecksum = 0; + for(int i = 0; i < rootDirectory.Length; i += Marshal.SizeOf()) { DirectoryEntry entry = @@ -404,6 +410,36 @@ namespace DiscImageChef.Filesystems.FAT if(entry.filename[0] == DIRENT_FINISHED) break; + if(entry.attributes.HasFlag(FatAttributes.LFN)) + { + if(this.@namespace != Namespace.Lfn) continue; + + LfnEntry lfnEntry = + Marshal.ByteArrayToStructureLittleEndian(rootDirectory, i, + 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(entry.filename[0] < DIRENT_MIN && entry.filename[0] != DIRENT_E5) continue; @@ -416,9 +452,6 @@ namespace DiscImageChef.Filesystems.FAT // Deleted if(entry.filename[0] == DIRENT_DELETED) continue; - // TODO: LFN namespace - if(entry.attributes.HasFlag(FatAttributes.LFN)) continue; - string filename; if(entry.attributes.HasFlag(FatAttributes.VolumeLabel)) @@ -449,6 +482,21 @@ namespace DiscImageChef.Filesystems.FAT continue; } + if(this.@namespace == Namespace.Lfn && lastLfnName != null) + { + byte calculatedLfnChecksum = LfnChecksum(entry.filename, entry.extension); + + if(calculatedLfnChecksum == lastLfnChecksum) + { + filename = StringHandlers.CToString(lastLfnName, Encoding.Unicode, true); + + rootDirectoryCache[filename] = entry; + lastLfnName = null; + lastLfnChecksum = 0; + continue; + } + } + if(entry.filename[0] == DIRENT_E5) entry.filename[0] = DIRENT_DELETED; string name = Encoding.GetString(entry.filename).TrimEnd(); @@ -466,7 +514,9 @@ namespace DiscImageChef.Filesystems.FAT if(extension != "") filename = name + "." + extension; else filename = name; - rootDirectoryCache.Add(filename, entry); + rootDirectoryCache[filename] = entry; + lastLfnName = null; + lastLfnChecksum = 0; } XmlFsType.VolumeName = XmlFsType.VolumeName?.Trim();