public FatStream(FatDirectoryEntry entry) { DirectoryEntry = entry ?? throw new ArgumentNullException(nameof(entry)); FS = entry.GetFileSystem(); FatTable = entry.GetFatTable(); Size = entry.Size; if (FatTable == null) { throw new Exception("The fat chain returned for the directory entry was null."); } }
public FatDirectoryEntry(FatFileSystem fileSystem, FatDirectoryEntry parent, string fullPath, long size, string name, uint firstCluster) : base(fileSystem, parent, fullPath, name, size, DirectoryEntryType.Directory) { if (firstCluster < fileSystem.RootCluster) { throw new ArgumentOutOfRangeException(nameof(firstCluster)); } FirstClusterNum = firstCluster; EntryHeaderDataOffset = 0; }
public FatDirectoryEntry FindVolumeId() { if (!IsRootDirectory()) { throw new Exception("VolumeId can be found only in Root Directory"); } var data = GetDirectoryEntryData(); var parent = this; FatDirectoryEntry result = null; for (var i = 0; i < data.Length; i += 32) { var attrib = data[i + 11]; if (attrib != FatDirectoryEntryAttributes.VolumeID) { continue; } // The Label in FAT could be only a shortName (limited to 11 characters) so it is more easy var name = Encoding.ASCII.GetString(data, i, 11); name = name.TrimEnd(); var fullPath = Path.Combine(FullPath, name); // Probably can be OK to hardcode 0 here var size = BitConverter.ToUInt32(data, i + 28); //var firstCluster = (uint)(data.ToUInt16(i + 20) << 16 | data.ToUInt16(i + 26)); var firstCluster = parent.FirstClusterNum; result = new FatDirectoryEntry(((FatFileSystem)FileSystem), parent, fullPath, name, size, firstCluster, (uint)i, DirectoryEntryType.File); break; } if (result == null) { Debugger.Log(0, "FileSystem", $"VolumeID not found, returning null"); } return(result); }
/// <summary> /// Retrieves a <see cref="List{T}"/> of <see cref="FatDirectoryEntry"/> objects that represent the Directory Entries inside this Directory /// </summary> /// <returns>Returns a <see cref="List{T}"/> of the Directory Entries inside this Directory</returns> public List <FatDirectoryEntry> ReadDirectoryContents(bool returnShortFilenames = false) { var data = GetDirectoryEntryData(); var result = new List <FatDirectoryEntry>(); FatDirectoryEntry parent = this; //TODO: Change longName to StringBuilder var longName = string.Empty; var name = string.Empty; for (var i = 0; i < data.Length; i += 32) { var attrib = data[i + 11]; var status = data[i]; if (attrib == FatDirectoryEntryAttributes.LongName) { var type = data[i + 12]; if (returnShortFilenames) { continue; } if (status == FatDirectoryEntryAttributes.UnusedOrDeletedEntry) { Debugger.Log(0, "FileSystem", $"<DELETED> : Attrib = {attrib}, Status = {status}"); continue; } if (type == 0) { if ((status & 0x40) > 0) { longName = string.Empty; } // TODO: Check LDIR_Ord for ordering and throw exception if entries are found out of order. // Also save buffer and only copy name if a end Ord marker is found. var longPart = Encoding.Unicode.GetString(data, i + 1, 10); // We have to check the length because 0xFFFF is a valid Unicode codepoint. So we only want to stop if the 0xFFFF is AFTER a 0x0000. We can determin // this by also looking at the length. Since we short circuit the or, the length is rarely evaluated. if (BitConverter.ToUInt16(data, i + 14) != 0xFFFF || longPart.Length == 5) { longPart += Encoding.Unicode.GetString(data, i + 14, 12); if (BitConverter.ToUInt16(data, i + 28) != 0xFFFF || longPart.Length == 11) { longPart += Encoding.Unicode.GetString(data, i + 28, 4); } } longName = longPart + longName; longPart = null; //TODO: LDIR_Chksum } } else { name = longName; if (status == 0x00) { Debugger.Log(0, "FileSystem", $"<EOF> : Attrib = {attrib}, Status = {status}"); break; } switch (status) { case 0x05: break; // Japanese characters - We dont handle these case 0x2E: continue; // Dot entry case FatDirectoryEntryAttributes.UnusedOrDeletedEntry: continue; // Empty slot, skip it default: var test = attrib & (FatDirectoryEntryAttributes.Directory | FatDirectoryEntryAttributes.VolumeID); if (status >= 0x20) { if (longName.Length > 0) { // Leading and trailing spaces are to be ignored according to spec. // Many programs (including Windows) pad trailing spaces although it is not required for long names. // As per spec, ignore trailing periods name = longName.Trim(new[] { '\0', '\uffff' }).Trim(); // If there are trailing periods var nameIndex = name.Length - 1; if (name[nameIndex] == '.') { // Search backwards till we find the first non-period character for (; nameIndex > 0; nameIndex--) { if (name[nameIndex] != '.') { break; } } // Substring to remove the periods name = name.Substring(0, nameIndex + 1); } longName = string.Empty; } else if (test == 0) { var entry = Encoding.ASCII.GetString(data, i, 11); name = entry.Substring(0, 8).TrimEnd(); var ext = entry.Substring(8, 3).TrimEnd(); if (ext.Length > 0) { name = name + "." + ext; } } else { name = Encoding.ASCII.GetString(data, i, 11).TrimEnd(); } } var firstCluster = (uint)(BitConverter.ToUInt16(data, i + 20) << 16 | BitConverter.ToUInt16(data, i + 26)); if (test == 0) { var size = BitConverter.ToUInt32(data, i + 28); if (size == 0 && name.Length == 0) { continue; } var fullPath = Path.Combine(FullPath, name); var entry = new FatDirectoryEntry(((FatFileSystem)FileSystem), parent, fullPath, name, size, firstCluster, (uint)i, DirectoryEntryType.File); result.Add(entry); } else if (test == FatDirectoryEntryAttributes.Directory) { var fullPath = Path.Combine(FullPath, name); var size = BitConverter.ToUInt32(data, (int)i + 28); var entry = new FatDirectoryEntry(((FatFileSystem)FileSystem), parent, fullPath, name, size, firstCluster, (uint)i, DirectoryEntryType.Directory); result.Add(entry); } else if (test == FatDirectoryEntryAttributes.VolumeID) { Debugger.Log(0, "FileSystem", $"<VOLUME ID> : Attrib = {attrib}, Status = {status}"); } else { Debugger.Log(0, "FileSystem", $"<INVALID ENTRY> : Attrib = {attrib}, Status = {status}"); } break; } } } return(result); }
public FatDirectoryEntry AddDirectoryEntry(string name, DirectoryEntryType entryType) { if (entryType == DirectoryEntryType.Directory || entryType == DirectoryEntryType.File) { var shortName = name; uint[] directoryEntriesToAllocate = null; var x1 = entryType == DirectoryEntryType.File; var x2 = name.Contains("."); var x3 = x2 ? name.Substring(0, name.LastIndexOf('.')).Contains(".") : false; var x4 = x2 ? name.Substring(0, name.IndexOf('.')).Length > 8 : false; var x5 = x2 ? name.Substring(name.IndexOf('.') + 1).Length > 3 : false; var x6 = entryType == DirectoryEntryType.Directory; var x7 = name.Length > 11; var x8 = x3 || (x4 || x5); var x9 = x2 && x8; var x10 = (x1 && x9) || (x6 && x7); if (x10) { var longName = name; var lastPeriodPosition = name.LastIndexOf('.'); var ext = string.Empty; // Only take the name until the first dot if (lastPeriodPosition + 1 > 0 && lastPeriodPosition + 1 < name.Length) { ext = shortName.Substring(lastPeriodPosition + 1); } // Remove all whitespaces and dots (except final) for (var i = shortName.Length - 1; i > 0; i--) { var c = shortName[i]; if (char.IsWhiteSpace(c) || (c == '.' && i != lastPeriodPosition)) { shortName.Remove(i, 1); } } var invalidShortNameChars = new[] { '"', '*', '+', ',', '.', '/', ':', ';', '<', '=', '>', '?', '[', '\\', ']', '|' }; // Remove all invalid characters foreach (var invalidChar in invalidShortNameChars) { shortName.Replace(invalidChar, '_'); } var n = 1; var directoryEntries = ReadDirectoryContents(true); var shortFilenames = new string[directoryEntries.Count]; for (var i = 0; i < directoryEntries.Count; i++) { shortFilenames[i] = directoryEntries[i].Name; } var nameTry = string.Empty; var test = false; do { nameTry = (shortName.Substring(0, 7 - n.ToString().Length) + "~" + n).ToUpperInvariant(); if (!string.IsNullOrEmpty(ext)) { nameTry += '.' + ext.ToUpperInvariant(); } n++; test = false; foreach (var name2 in shortFilenames) { if (name2 == nameTry) { test = true; break; } } } //while (Array.IndexOf((Array)xShortFilenames, xNameTry) != -1); //TODO: use the generic version of IndexOf, just remove the cast to Array while (test); shortName = nameTry; var checksum = CalculateChecksum(GetShortName(shortName)); var numEntries = (int)Math.Ceiling(longName.Length / 13d); var longNameWithPad = new char[numEntries * 13]; longNameWithPad[longNameWithPad.Length - 1] = (char)0xFFFF; Array.Copy(longName.ToCharArray(), longNameWithPad, longName.Length); directoryEntriesToAllocate = GetNextUnallocatedDirectoryEntries(numEntries + 1); for (var i = numEntries - 1; i >= 0; i--) { var entry = directoryEntriesToAllocate[numEntries - i - 1]; SetLongFilenameEntryMetadataValue(entry, FatDirectoryEntryMetadata.LongFilenameEntryMetadata.SequenceNumberAndAllocationStatus, (i + 1) | (i == numEntries - 1 ? (1 << 6) : 0)); SetLongFilenameEntryMetadataValue(entry, FatDirectoryEntryMetadata.LongFilenameEntryMetadata.Attributes, FatDirectoryEntryAttributes.LongName); SetLongFilenameEntryMetadataValue(entry, FatDirectoryEntryMetadata.LongFilenameEntryMetadata.Checksum, checksum); var a1 = new string(longNameWithPad, i * 13, 5); var a2 = new string(longNameWithPad, i * 13 + 5, 6); var a3 = new string(longNameWithPad, i * 13 + 11, 2); SetLongFilenameEntryMetadataValue(entry, FatDirectoryEntryMetadata.LongFilenameEntryMetadata.LongName1, a1); SetLongFilenameEntryMetadataValue(entry, FatDirectoryEntryMetadata.LongFilenameEntryMetadata.LongName2, a2); SetLongFilenameEntryMetadataValue(entry, FatDirectoryEntryMetadata.LongFilenameEntryMetadata.LongName3, a3); } } var fullPath = Path.Combine(FullPath, name); var firstCluster = ((FatFileSystem)FileSystem).GetFat(0).GetNextUnallocatedFatEntry(); var entryHeaderDataOffset = directoryEntriesToAllocate == null?GetNextUnallocatedDirectoryEntry() : directoryEntriesToAllocate[directoryEntriesToAllocate.Length - 1]; var newEntry = new FatDirectoryEntry((FatFileSystem)FileSystem, this, fullPath, name, 0, firstCluster, entryHeaderDataOffset, entryType); newEntry.AllocateDirectoryEntry(shortName); return(newEntry); } throw new ArgumentOutOfRangeException(nameof(entryType), "Unknown directory entry type."); }