mirror of
https://github.com/aaru-dps/Aaru.git
synced 2025-12-16 19:24:25 +00:00
Implement OS/2 and eCS namespaces using .LONGNAME EA in FAT12 and FAT16.
This commit is contained in:
@@ -83,6 +83,15 @@ namespace DiscImageChef.Filesystems.FAT
|
||||
const ushort EADATA_MAGIC = 0x4445;
|
||||
const ushort EASCTR_MAGIC = 0x4145;
|
||||
const ushort EA_UNUSED = 0xFFFF;
|
||||
const ushort EAT_BINARY = 0xFFFE;
|
||||
const ushort EAT_ASCII = 0xFFFD;
|
||||
const ushort EAT_BITMAP = 0xFFFB;
|
||||
const ushort EAT_METAFILE = 0xFFFA;
|
||||
const ushort EAT_ICON = 0xFFF9;
|
||||
const ushort EAT_EA = 0xFFEE;
|
||||
const ushort EAT_MVMT = 0xFFDF;
|
||||
const ushort EAT_MVST = 0xFFDE;
|
||||
const ushort EAT_ASN1 = 0xFFDD;
|
||||
|
||||
readonly (string hash, string name)[] knownBootHashes =
|
||||
{
|
||||
@@ -216,7 +225,9 @@ namespace DiscImageChef.Filesystems.FAT
|
||||
{
|
||||
Dos,
|
||||
Nt,
|
||||
Lfn
|
||||
Lfn,
|
||||
Os2,
|
||||
Ecs
|
||||
}
|
||||
|
||||
[Flags]
|
||||
|
||||
@@ -122,8 +122,9 @@ namespace DiscImageChef.Filesystems.FAT
|
||||
}
|
||||
|
||||
currentDirectory = new Dictionary<string, DirectoryEntry>();
|
||||
byte[] lastLfnName = null;
|
||||
byte lastLfnChecksum = 0;
|
||||
byte[] lastLfnName = null;
|
||||
byte lastLfnChecksum = 0;
|
||||
List<string> LFNs = new List<string>();
|
||||
|
||||
for(int pos = 0; pos < directoryBuffer.Length; pos += Marshal.SizeOf<DirectoryEntry>())
|
||||
{
|
||||
@@ -187,6 +188,7 @@ namespace DiscImageChef.Filesystems.FAT
|
||||
{
|
||||
filename = StringHandlers.CToString(lastLfnName, Encoding.Unicode, true);
|
||||
|
||||
LFNs.Add(filename);
|
||||
currentDirectory[filename] = dirent;
|
||||
lastLfnName = null;
|
||||
lastLfnChecksum = 0;
|
||||
@@ -217,6 +219,45 @@ namespace DiscImageChef.Filesystems.FAT
|
||||
currentDirectory[filename] = dirent;
|
||||
}
|
||||
|
||||
// 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();
|
||||
|
||||
foreach(KeyValuePair<string, DirectoryEntry> 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);
|
||||
|
||||
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');
|
||||
|
||||
currentDirectory.Remove(fileWithEa.Key);
|
||||
currentDirectory[longname] = fileWithEa.Value;
|
||||
}
|
||||
}
|
||||
|
||||
directoryCache.Add(currentPath, currentDirectory);
|
||||
}
|
||||
|
||||
|
||||
@@ -80,7 +80,9 @@ namespace DiscImageChef.Filesystems.FAT
|
||||
{
|
||||
{"dos", "DOS (8.3 all uppercase)"},
|
||||
{"nt", "Windows NT (8.3 mixed case)"},
|
||||
{"lfn", "Long file names (default)"}
|
||||
{"os2", "OS/2 .LONGNAME extended attribute"},
|
||||
{"ecs", "Use LFN when available with fallback to .LONGNAME (default)"},
|
||||
{"lfn", "Long file names"}
|
||||
};
|
||||
|
||||
static Dictionary<string, string> GetDefaultOptions() =>
|
||||
|
||||
@@ -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 = "lfn";
|
||||
if(@namespace is null) @namespace = "ecs";
|
||||
|
||||
switch(@namespace.ToLowerInvariant())
|
||||
{
|
||||
@@ -76,6 +76,12 @@ namespace DiscImageChef.Filesystems.FAT
|
||||
case "nt":
|
||||
this.@namespace = Namespace.Nt;
|
||||
break;
|
||||
case "os2":
|
||||
this.@namespace = Namespace.Os2;
|
||||
break;
|
||||
case "ecs":
|
||||
this.@namespace = Namespace.Ecs;
|
||||
break;
|
||||
case "lfn":
|
||||
this.@namespace = Namespace.Lfn;
|
||||
break;
|
||||
@@ -399,8 +405,9 @@ namespace DiscImageChef.Filesystems.FAT
|
||||
|
||||
if(rootDirectory is null) return Errno.InvalidArgument;
|
||||
|
||||
byte[] lastLfnName = null;
|
||||
byte lastLfnChecksum = 0;
|
||||
byte[] lastLfnName = null;
|
||||
byte lastLfnChecksum = 0;
|
||||
List<string> LFNs = new List<string>();
|
||||
|
||||
for(int i = 0; i < rootDirectory.Length; i += Marshal.SizeOf<DirectoryEntry>())
|
||||
{
|
||||
@@ -412,7 +419,7 @@ namespace DiscImageChef.Filesystems.FAT
|
||||
|
||||
if(entry.attributes.HasFlag(FatAttributes.LFN))
|
||||
{
|
||||
if(this.@namespace != Namespace.Lfn) continue;
|
||||
if(this.@namespace != Namespace.Lfn && this.@namespace != Namespace.Ecs) continue;
|
||||
|
||||
LfnEntry lfnEntry =
|
||||
Marshal.ByteArrayToStructureLittleEndian<LfnEntry>(rootDirectory, i,
|
||||
@@ -482,7 +489,7 @@ namespace DiscImageChef.Filesystems.FAT
|
||||
continue;
|
||||
}
|
||||
|
||||
if(this.@namespace == Namespace.Lfn && lastLfnName != null)
|
||||
if((this.@namespace == Namespace.Lfn || this.@namespace == Namespace.Ecs) && lastLfnName != null)
|
||||
{
|
||||
byte calculatedLfnChecksum = LfnChecksum(entry.filename, entry.extension);
|
||||
|
||||
@@ -490,6 +497,7 @@ namespace DiscImageChef.Filesystems.FAT
|
||||
{
|
||||
filename = StringHandlers.CToString(lastLfnName, Encoding.Unicode, true);
|
||||
|
||||
LFNs.Add(filename);
|
||||
rootDirectoryCache[filename] = entry;
|
||||
lastLfnName = null;
|
||||
lastLfnChecksum = 0;
|
||||
@@ -616,6 +624,45 @@ namespace DiscImageChef.Filesystems.FAT
|
||||
else 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();
|
||||
|
||||
foreach(KeyValuePair<string, DirectoryEntry> 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);
|
||||
|
||||
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');
|
||||
|
||||
rootDirectoryCache.Remove(fileWithEa.Key);
|
||||
rootDirectoryCache[longname] = fileWithEa.Value;
|
||||
}
|
||||
}
|
||||
|
||||
mounted = true;
|
||||
return Errno.NoError;
|
||||
}
|
||||
|
||||
@@ -74,58 +74,9 @@ namespace DiscImageChef.Filesystems.FAT
|
||||
|
||||
if(entry.ea_handle == 0) return Errno.NoError;
|
||||
|
||||
int aIndex = entry.ea_handle >> 7;
|
||||
// First 0x20 bytes are the magic number and unused words
|
||||
ushort a = BitConverter.ToUInt16(cachedEaData, aIndex * 2 + 0x20);
|
||||
eas = GetEas(entry.ea_handle);
|
||||
|
||||
ushort b = BitConverter.ToUInt16(cachedEaData, entry.ea_handle * 2 + 0x200);
|
||||
|
||||
uint eaCluster = (uint)(a + b);
|
||||
|
||||
if(b == EA_UNUSED) return Errno.NoError;
|
||||
|
||||
EaHeader header =
|
||||
Marshal.ByteArrayToStructureLittleEndian<EaHeader>(cachedEaData, (int)(eaCluster * bytesPerCluster),
|
||||
Marshal.SizeOf<EaHeader>());
|
||||
|
||||
if(header.magic != 0x4145) return Errno.NoError;
|
||||
|
||||
uint eaLen = BitConverter.ToUInt32(cachedEaData,
|
||||
(int)(eaCluster * bytesPerCluster) + Marshal.SizeOf<EaHeader>());
|
||||
|
||||
byte[] eaData = new byte[eaLen];
|
||||
Array.Copy(cachedEaData, (int)(eaCluster * bytesPerCluster) + Marshal.SizeOf<EaHeader>(), eaData, 0, eaLen);
|
||||
|
||||
eas = new Dictionary<string, byte[]>();
|
||||
|
||||
if(true) eas.Add("com.microsoft.os2.fea", eaData);
|
||||
|
||||
int pos = 4;
|
||||
while(pos < eaData.Length)
|
||||
{
|
||||
byte fEA = eaData[pos++];
|
||||
byte cbName = eaData[pos++];
|
||||
ushort cbValue = BitConverter.ToUInt16(eaData, pos);
|
||||
pos += 2;
|
||||
|
||||
string name = Encoding.ASCII.GetString(eaData, pos, cbName);
|
||||
pos += cbName;
|
||||
pos++;
|
||||
byte[] data = new byte[cbValue];
|
||||
|
||||
Array.Copy(eaData, pos, data, 0, cbValue);
|
||||
pos += cbValue;
|
||||
|
||||
// OS/2 System Attributes
|
||||
if(name[0] == '.')
|
||||
{
|
||||
// This is WorkPlace System information so it's IBM
|
||||
if(name == ".CLASSINFO") name = "com.ibm.os2.classinfo";
|
||||
else name = "com.microsoft.os2" + name.ToLower();
|
||||
}
|
||||
|
||||
eas.Add(name, data);
|
||||
}
|
||||
if(eas is null) return Errno.NoError;
|
||||
|
||||
eaCache.Add(path.ToLower(cultureInfo), eas);
|
||||
xattrs = eas.Keys.ToList();
|
||||
@@ -162,6 +113,64 @@ namespace DiscImageChef.Filesystems.FAT
|
||||
return Errno.NoError;
|
||||
}
|
||||
|
||||
Dictionary<string, byte[]> GetEas(ushort eaHandle)
|
||||
{
|
||||
int aIndex = eaHandle >> 7;
|
||||
// First 0x20 bytes are the magic number and unused words
|
||||
ushort a = BitConverter.ToUInt16(cachedEaData, aIndex * 2 + 0x20);
|
||||
|
||||
ushort b = BitConverter.ToUInt16(cachedEaData, eaHandle * 2 + 0x200);
|
||||
|
||||
uint eaCluster = (uint)(a + b);
|
||||
|
||||
if(b == EA_UNUSED) return null;
|
||||
|
||||
EaHeader header =
|
||||
Marshal.ByteArrayToStructureLittleEndian<EaHeader>(cachedEaData, (int)(eaCluster * bytesPerCluster),
|
||||
Marshal.SizeOf<EaHeader>());
|
||||
|
||||
if(header.magic != 0x4145) return null;
|
||||
|
||||
uint eaLen = BitConverter.ToUInt32(cachedEaData,
|
||||
(int)(eaCluster * bytesPerCluster) + Marshal.SizeOf<EaHeader>());
|
||||
|
||||
byte[] eaData = new byte[eaLen];
|
||||
Array.Copy(cachedEaData, (int)(eaCluster * bytesPerCluster) + Marshal.SizeOf<EaHeader>(), eaData, 0, eaLen);
|
||||
|
||||
Dictionary<string, byte[]> eas = new Dictionary<string, byte[]>();
|
||||
|
||||
if(debug) eas.Add("com.microsoft.os2.fea", eaData);
|
||||
|
||||
int pos = 4;
|
||||
while(pos < eaData.Length)
|
||||
{
|
||||
byte fEA = eaData[pos++];
|
||||
byte cbName = eaData[pos++];
|
||||
ushort cbValue = BitConverter.ToUInt16(eaData, pos);
|
||||
pos += 2;
|
||||
|
||||
string name = Encoding.ASCII.GetString(eaData, pos, cbName);
|
||||
pos += cbName;
|
||||
pos++;
|
||||
byte[] data = new byte[cbValue];
|
||||
|
||||
Array.Copy(eaData, pos, data, 0, cbValue);
|
||||
pos += cbValue;
|
||||
|
||||
// OS/2 System Attributes
|
||||
if(name[0] == '.')
|
||||
{
|
||||
// This is WorkPlace System information so it's IBM
|
||||
if(name == ".CLASSINFO") name = "com.ibm.os2.classinfo";
|
||||
else name = "com.microsoft.os2" + name.ToLower();
|
||||
}
|
||||
|
||||
eas.Add(name, data);
|
||||
}
|
||||
|
||||
return eas;
|
||||
}
|
||||
|
||||
void CacheEaData()
|
||||
{
|
||||
if(eaDirEntry.start_cluster == 0) return;
|
||||
|
||||
Reference in New Issue
Block a user