Implement LFN namespace in FAT.

This commit is contained in:
2019-04-27 13:04:59 +01:00
parent 511378a6be
commit c12b0d91db
6 changed files with 138 additions and 14 deletions

View File

@@ -77,6 +77,9 @@ namespace DiscImageChef.Filesystems.FAT
const ushort FAT12_END_MASK = 0xFF8; const ushort FAT12_END_MASK = 0xFF8;
const ushort FAT12_FORMATTED = 0xFF6; const ushort FAT12_FORMATTED = 0xFF6;
const ushort FAT12_BAD = 0xFF7; 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 = readonly (string hash, string name)[] knownBootHashes =
{ {
@@ -209,7 +212,8 @@ namespace DiscImageChef.Filesystems.FAT
enum Namespace enum Namespace
{ {
Dos, Dos,
Nt Nt,
Lfn
} }
} }
} }

View File

@@ -34,6 +34,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using System.Text;
using DiscImageChef.CommonTypes.Structs; using DiscImageChef.CommonTypes.Structs;
using DiscImageChef.Helpers; using DiscImageChef.Helpers;
@@ -118,18 +119,47 @@ namespace DiscImageChef.Filesystems.FAT
} }
currentDirectory = new Dictionary<string, DirectoryEntry>(); currentDirectory = new Dictionary<string, DirectoryEntry>();
byte[] lastLfnName = null;
byte lastLfnChecksum = 0;
int pos = 0; for(int pos = 0; pos < directoryBuffer.Length; pos += Marshal.SizeOf<DirectoryEntry>())
while(pos < directoryBuffer.Length)
{ {
DirectoryEntry dirent = DirectoryEntry dirent =
Marshal.ByteArrayToStructureLittleEndian<DirectoryEntry>(directoryBuffer, pos, Marshal.ByteArrayToStructureLittleEndian<DirectoryEntry>(directoryBuffer, pos,
Marshal.SizeOf<DirectoryEntry>()); Marshal.SizeOf<DirectoryEntry>());
pos += Marshal.SizeOf<DirectoryEntry>();
if(dirent.filename[0] == DIRENT_FINISHED) break; if(dirent.filename[0] == DIRENT_FINISHED) break;
if(dirent.attributes.HasFlag(FatAttributes.LFN))
{
if(@namespace != Namespace.Lfn) continue;
LfnEntry lfnEntry =
Marshal.ByteArrayToStructureLittleEndian<LfnEntry>(directoryBuffer, pos,
Marshal.SizeOf<LfnEntry>());
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 // Not a correct entry
if(dirent.filename[0] < DIRENT_MIN && dirent.filename[0] != DIRENT_E5) continue; if(dirent.filename[0] < DIRENT_MIN && dirent.filename[0] != DIRENT_E5) continue;
@@ -142,13 +172,25 @@ namespace DiscImageChef.Filesystems.FAT
// Deleted // Deleted
if(dirent.filename[0] == DIRENT_DELETED) continue; if(dirent.filename[0] == DIRENT_DELETED) continue;
// TODO: LFN namespace
if(dirent.attributes.HasFlag(FatAttributes.LFN)) continue;
string filename; string filename;
if(dirent.attributes.HasFlag(FatAttributes.VolumeLabel)) continue; 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; if(dirent.filename[0] == DIRENT_E5) dirent.filename[0] = DIRENT_DELETED;
string name = Encoding.GetString(dirent.filename).TrimEnd(); string name = Encoding.GetString(dirent.filename).TrimEnd();

View File

@@ -76,7 +76,9 @@ namespace DiscImageChef.Filesystems.FAT
public Dictionary<string, string> Namespaces => public Dictionary<string, string> Namespaces =>
new Dictionary<string, string> new Dictionary<string, string>
{ {
{"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<string, string> GetDefaultOptions() => static Dictionary<string, string> GetDefaultOptions() =>

View File

@@ -233,5 +233,15 @@ namespace DiscImageChef.Filesystems.FAT
entry = dirent.Value; entry = dirent.Value;
return Errno.NoError; 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;
}
} }
} }

View File

@@ -866,5 +866,21 @@ namespace DiscImageChef.Filesystems.FAT
public readonly ushort start_cluster; public readonly ushort start_cluster;
public readonly uint size; 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;
}
} }
} }

View File

@@ -66,7 +66,7 @@ namespace DiscImageChef.Filesystems.FAT
if(options.TryGetValue("debug", out string debugString)) bool.TryParse(debugString, out debug); if(options.TryGetValue("debug", out string debugString)) bool.TryParse(debugString, out debug);
// Default namespace // Default namespace
if(@namespace is null) @namespace = "nt"; if(@namespace is null) @namespace = "lfn";
switch(@namespace.ToLowerInvariant()) switch(@namespace.ToLowerInvariant())
{ {
@@ -76,6 +76,9 @@ namespace DiscImageChef.Filesystems.FAT
case "nt": case "nt":
this.@namespace = Namespace.Nt; this.@namespace = Namespace.Nt;
break; break;
case "lfn":
this.@namespace = Namespace.Lfn;
break;
default: return Errno.InvalidArgument; default: return Errno.InvalidArgument;
} }
@@ -396,6 +399,9 @@ namespace DiscImageChef.Filesystems.FAT
if(rootDirectory is null) return Errno.InvalidArgument; if(rootDirectory is null) return Errno.InvalidArgument;
byte[] lastLfnName = null;
byte lastLfnChecksum = 0;
for(int i = 0; i < rootDirectory.Length; i += Marshal.SizeOf<DirectoryEntry>()) for(int i = 0; i < rootDirectory.Length; i += Marshal.SizeOf<DirectoryEntry>())
{ {
DirectoryEntry entry = DirectoryEntry entry =
@@ -404,6 +410,36 @@ namespace DiscImageChef.Filesystems.FAT
if(entry.filename[0] == DIRENT_FINISHED) break; if(entry.filename[0] == DIRENT_FINISHED) break;
if(entry.attributes.HasFlag(FatAttributes.LFN))
{
if(this.@namespace != Namespace.Lfn) continue;
LfnEntry lfnEntry =
Marshal.ByteArrayToStructureLittleEndian<LfnEntry>(rootDirectory, i,
Marshal.SizeOf<LfnEntry>());
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 // Not a correct entry
if(entry.filename[0] < DIRENT_MIN && entry.filename[0] != DIRENT_E5) continue; if(entry.filename[0] < DIRENT_MIN && entry.filename[0] != DIRENT_E5) continue;
@@ -416,9 +452,6 @@ namespace DiscImageChef.Filesystems.FAT
// Deleted // Deleted
if(entry.filename[0] == DIRENT_DELETED) continue; if(entry.filename[0] == DIRENT_DELETED) continue;
// TODO: LFN namespace
if(entry.attributes.HasFlag(FatAttributes.LFN)) continue;
string filename; string filename;
if(entry.attributes.HasFlag(FatAttributes.VolumeLabel)) if(entry.attributes.HasFlag(FatAttributes.VolumeLabel))
@@ -449,6 +482,21 @@ namespace DiscImageChef.Filesystems.FAT
continue; 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; if(entry.filename[0] == DIRENT_E5) entry.filename[0] = DIRENT_DELETED;
string name = Encoding.GetString(entry.filename).TrimEnd(); string name = Encoding.GetString(entry.filename).TrimEnd();
@@ -466,7 +514,9 @@ namespace DiscImageChef.Filesystems.FAT
if(extension != "") filename = name + "." + extension; if(extension != "") filename = name + "." + extension;
else filename = name; else filename = name;
rootDirectoryCache.Add(filename, entry); rootDirectoryCache[filename] = entry;
lastLfnName = null;
lastLfnChecksum = 0;
} }
XmlFsType.VolumeName = XmlFsType.VolumeName?.Trim(); XmlFsType.VolumeName = XmlFsType.VolumeName?.Trim();