├── .gitignore ├── LineOS.sln ├── LineOS ├── CLI │ ├── CommandHandler.cs │ ├── Commands │ │ ├── CmdChgDir.cs │ │ ├── CmdDir.cs │ │ ├── CmdGui.cs │ │ ├── CmdHelp.cs │ │ ├── CmdKbdLayout.cs │ │ ├── CmdReadFile.cs │ │ ├── CmdReboot.cs │ │ ├── CmdShutdown.cs │ │ └── CmdVolumeList.cs │ ├── ConsoleManager.cs │ ├── ICommand.cs │ └── TablePrinter.cs ├── FS │ ├── BlockDeviceStream.cs │ └── NtfsAwareCosmosVFS.cs ├── Kernel.cs ├── LineOS.csproj └── NTFS │ ├── Compression │ ├── CompressionResult.cs │ ├── LZNT1.cs │ └── LzWindowDictionary.cs │ ├── Cosmos │ ├── NtfsDirectoryEntry.cs │ ├── NtfsFileSystem.cs │ └── NtfsFileSystemFactory.cs │ ├── IO │ ├── DataFragmentComparer.cs │ ├── NtfsDirectory.cs │ ├── NtfsDiskStream.cs │ ├── NtfsFile.cs │ ├── NtfsFileCache.cs │ └── NtfsFileEntry.cs │ ├── Model │ ├── Attributes │ │ ├── Attribute.cs │ │ ├── AttributeBitmap.cs │ │ ├── AttributeData.cs │ │ ├── AttributeExtendedAttributeInformation.cs │ │ ├── AttributeExtendedAttributes.cs │ │ ├── AttributeFileName.cs │ │ ├── AttributeGeneric.cs │ │ ├── AttributeIndexAllocation.cs │ │ ├── AttributeIndexRoot.cs │ │ ├── AttributeList.cs │ │ ├── AttributeListItem.cs │ │ ├── AttributeLoggedUtilityStream.cs │ │ ├── AttributeObjectId.cs │ │ ├── AttributeSecurityDescriptor.cs │ │ ├── AttributeStandardInformation.cs │ │ ├── AttributeVolumeInformation.cs │ │ ├── AttributeVolumeName.cs │ │ └── ExtendedAttribute.cs │ ├── DataFragment.cs │ ├── Enums │ │ ├── AttributeFlags.cs │ │ ├── AttributeResidentAllow.cs │ │ ├── AttributeType.cs │ │ ├── FileEntryFlags.cs │ │ ├── FileNamespace.cs │ │ ├── MFTEAFlags.cs │ │ ├── MFTIndexEntryFlags.cs │ │ ├── MFTIndexRootFlags.cs │ │ ├── MetadataMftFiles.cs │ │ ├── ResidentFlag.cs │ │ └── VolumeInformationFlags.cs │ ├── FileRecord.cs │ ├── FileReference.cs │ ├── Headers │ │ ├── AttributeNonResidentHeader.cs │ │ └── AttributeResidentHeader.cs │ ├── ISaveableObject.cs │ ├── IndexAllocationChunk.cs │ └── IndexEntry.cs │ ├── Ntfs.cs │ ├── Parser │ ├── BootSector.cs │ ├── FileNamespaceComparer.cs │ └── NtfsParser.cs │ └── Utility │ ├── CompatDictionary.cs │ ├── LittleEndianConverter.cs │ ├── NtfsUtil.cs │ └── Util.cs └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | obj/ 2 | bin/ 3 | .vs -------------------------------------------------------------------------------- /LineOS.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.28010.2046 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LineOS", "LineOS\LineOS.csproj", "{6E48EC4A-C024-47DC-8FED-7E3238C43624}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {6E48EC4A-C024-47DC-8FED-7E3238C43624}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {6E48EC4A-C024-47DC-8FED-7E3238C43624}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {6E48EC4A-C024-47DC-8FED-7E3238C43624}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {6E48EC4A-C024-47DC-8FED-7E3238C43624}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {95B0BDB6-D884-4CC1-8F7B-FE1A03B51752} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /LineOS/CLI/CommandHandler.cs: -------------------------------------------------------------------------------- 1 | using LineOS.CLI.Commands; 2 | using System; 3 | 4 | namespace LineOS.CLI 5 | { 6 | public class CommandHandler 7 | { 8 | private static readonly string[] EmptyArray = new string[0]; 9 | 10 | public ICommand[] Commands { get; } = { 11 | new CmdShutdown(), 12 | new CmdReboot(), 13 | new CmdDir(), 14 | new CmdChgDir(), 15 | new CmdVolumeList(), 16 | new CmdKbdLayout(), 17 | new CmdHelp(), 18 | new CmdGui(), 19 | new CmdReadFile(), 20 | new CmdClearScreen() 21 | }; 22 | 23 | public void HandleCommand(string command) 24 | { 25 | var arr = command.Trim().Split(' '); 26 | var name = arr[0]; 27 | var args = arr.Length > 1 ? new string[arr.Length - 1] : EmptyArray; 28 | if (args.Length > 0) 29 | Array.Copy(arr, 1, args, 0, args.Length); 30 | foreach (var cmd in Commands) 31 | if (cmd.Name.ToLower() == name) 32 | { 33 | if (!cmd.Execute(args)) 34 | Console.WriteLine("Syntax: " + cmd.Syntax); 35 | return; 36 | } 37 | Console.WriteLine("SYNTAX ERROR"); 38 | } 39 | 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /LineOS/CLI/Commands/CmdChgDir.cs: -------------------------------------------------------------------------------- 1 | namespace LineOS.CLI.Commands 2 | { 3 | public class CmdChgDir : ICommand 4 | { 5 | public string Name => "cd"; 6 | 7 | public string Description => "Change current path"; 8 | 9 | public string Syntax => "cd "; 10 | 11 | public bool Execute(string[] args) 12 | { 13 | if (args.Length != 1) return false; 14 | 15 | var cdArg = ""; 16 | foreach (var a in args) 17 | cdArg += a + " "; 18 | cdArg = cdArg.Trim(); 19 | 20 | var newPath = Kernel.ConsoleManager.CurrentPath; 21 | 22 | if (cdArg.Contains(":")) 23 | newPath = cdArg; 24 | else if (cdArg.StartsWith("\\")) 25 | newPath = newPath.Remove(2) + cdArg; 26 | else if (cdArg == "..") 27 | { 28 | if (!newPath.EndsWith("\\")) 29 | newPath = newPath.Remove(LastIndexOf(newPath, '\\')); 30 | } 31 | else 32 | newPath += (newPath.EndsWith("\\") ? "" : "\\") + cdArg; 33 | 34 | CheckPath(newPath); 35 | Kernel.ConsoleManager.CurrentPath = newPath; 36 | 37 | return true; 38 | } 39 | 40 | private static int LastIndexOf(string str, char chr) 41 | { 42 | for (var i = str.Length - 1; i >= 0; i--) 43 | if (str[i] == chr) 44 | return i; 45 | return -1; 46 | } 47 | 48 | private static void CheckPath(string path) 49 | { 50 | Kernel.FileSystem.GetDirectoryListing(path); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /LineOS/CLI/Commands/CmdDir.cs: -------------------------------------------------------------------------------- 1 | using Cosmos.System.FileSystem.Listing; 2 | using System; 3 | 4 | namespace LineOS.CLI.Commands 5 | { 6 | public class CmdDir : ICommand 7 | { 8 | public string Name => "dir"; 9 | 10 | public string Description => "Lists contents of current directory"; 11 | 12 | public string Syntax => "dir <-l>"; 13 | 14 | public bool Execute(string[] args) 15 | { 16 | var listing = Kernel.FileSystem.GetDirectoryListing(Kernel.ConsoleManager.CurrentPath); 17 | var extended = args.Length == 1 && args[0] == "-l"; 18 | if (!extended) 19 | { 20 | Console.ForegroundColor = ConsoleColor.Yellow; 21 | foreach (var f in listing) 22 | if (f.mEntryType == DirectoryEntryTypeEnum.Directory) 23 | Console.Write(f.mName + "\t"); 24 | Console.ResetColor(); 25 | foreach (var f in listing) 26 | if (f.mEntryType == DirectoryEntryTypeEnum.File) 27 | Console.Write(f.mName + "\t"); 28 | Console.ResetColor(); 29 | Console.WriteLine(); 30 | } 31 | else 32 | { 33 | Kernel.TablePrinter.WriteHeaders("Type", "Size ", "Name"); 34 | foreach (var f in listing) 35 | { 36 | var type = f.mEntryType == DirectoryEntryTypeEnum.Directory ? "" : ""; 37 | Kernel.TablePrinter.WriteRow(type, "", f.mName); 38 | } 39 | } 40 | return true; 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /LineOS/CLI/Commands/CmdGui.cs: -------------------------------------------------------------------------------- 1 | using System.Drawing; 2 | using Cosmos.System.Graphics; 3 | 4 | namespace LineOS.CLI.Commands 5 | { 6 | public class CmdGui : ICommand 7 | { 8 | public string Name => "gui"; 9 | public string Description => "Starts the GUI for LineOS"; 10 | public string Syntax => "gui"; 11 | 12 | public bool Execute(string[] args) 13 | { 14 | var canvas = FullScreenCanvas.GetFullScreenCanvas(new Mode(1366, 768, ColorDepth.ColorDepth32)); 15 | canvas.Clear(Color.Green); 16 | return true; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /LineOS/CLI/Commands/CmdHelp.cs: -------------------------------------------------------------------------------- 1 | namespace LineOS.CLI.Commands 2 | { 3 | public class CmdHelp : ICommand 4 | { 5 | public string Name => "help"; 6 | 7 | public string Description => "Show a list of available commands"; 8 | 9 | public string Syntax => "help"; 10 | 11 | public bool Execute(string[] args) 12 | { 13 | Kernel.TablePrinter.WriteHeaders("Command", "Description"); 14 | foreach(var cmd in Kernel.ConsoleManager.CommandHandler.Commands) 15 | Kernel.TablePrinter.WriteRow(cmd.Name, cmd.Description); 16 | return true; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /LineOS/CLI/Commands/CmdKbdLayout.cs: -------------------------------------------------------------------------------- 1 | using Cosmos.System; 2 | using Cosmos.System.ScanMaps; 3 | 4 | namespace LineOS.CLI.Commands 5 | { 6 | public class CmdKbdLayout : ICommand 7 | { 8 | public string Name => "kbd"; 9 | 10 | public string Description => "Switch keyboard layout"; 11 | 12 | public string Syntax => "kbd "; 13 | 14 | public bool Execute(string[] args) 15 | { 16 | if (args.Length != 1) 17 | return false; 18 | var layout = args[0].ToLower(); 19 | switch (layout) 20 | { 21 | case "us": 22 | KeyboardManager.SetKeyLayout(new US_Standard()); 23 | break; 24 | case "de": 25 | KeyboardManager.SetKeyLayout(new DE_Standard()); 26 | break; 27 | case "fr": 28 | KeyboardManager.SetKeyLayout(new FR_Standard()); 29 | break; 30 | default: 31 | System.Console.WriteLine("Unknown keyboard layout " + layout); 32 | break; 33 | } 34 | return true; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /LineOS/CLI/Commands/CmdReadFile.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | 4 | namespace LineOS.CLI.Commands 5 | { 6 | public class CmdReadFile : ICommand 7 | { 8 | public string Name => "rdfile"; 9 | public string Description => "Read the contents of a file"; 10 | public string Syntax => "rdfile "; 11 | 12 | public bool Execute(string[] args) 13 | { 14 | if (args.Length != 1) return false; 15 | 16 | var stream = Kernel.FileSystem.GetFile(args[0]).GetFileStream(); 17 | var arr = new byte[512]; 18 | stream.Read(arr, 0, arr.Length); 19 | Console.WriteLine(Encoding.ASCII.GetString(arr)); 20 | return true; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /LineOS/CLI/Commands/CmdReboot.cs: -------------------------------------------------------------------------------- 1 | using Cosmos.System; 2 | 3 | namespace LineOS.CLI.Commands 4 | { 5 | public class CmdReboot : ICommand 6 | { 7 | public string Name => "reboot"; 8 | 9 | public string Description => "Restarts the computer"; 10 | 11 | public string Syntax => "reboot"; 12 | 13 | public bool Execute(string[] args) 14 | { 15 | Power.Reboot(); 16 | return true; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /LineOS/CLI/Commands/CmdShutdown.cs: -------------------------------------------------------------------------------- 1 | using Cosmos.System; 2 | 3 | namespace LineOS.CLI.Commands 4 | { 5 | public class CmdShutdown : ICommand 6 | { 7 | public string Name => "shutdown"; 8 | 9 | public string Description => "Shuts down LineOS"; 10 | 11 | public string Syntax => "shutdown"; 12 | 13 | public bool Execute(string[] args) 14 | { 15 | Power.Shutdown(); 16 | return true; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /LineOS/CLI/Commands/CmdVolumeList.cs: -------------------------------------------------------------------------------- 1 | using Cosmos.System.FileSystem.VFS; 2 | using System; 3 | 4 | namespace LineOS.CLI.Commands 5 | { 6 | public class CmdVolumeList : ICommand 7 | { 8 | public string Name => "vol"; 9 | 10 | public string Description => "List all available volumes"; 11 | 12 | public string Syntax => "vol"; 13 | 14 | public bool Execute(string[] args) 15 | { 16 | var vol = VFSManager.GetVolumes(); 17 | if (vol.Count > 0) 18 | { 19 | Kernel.TablePrinter.WriteHeaders("Volume #", "File System", "Size"); 20 | foreach (var v in vol) 21 | Kernel.TablePrinter.WriteRow(v.mName, Kernel.FileSystem.GetFileSystemType(v.mName), FormatSize(Kernel.FileSystem.GetTotalSize(v.mName))); 22 | } 23 | else 24 | Console.WriteLine("No volumes found"); 25 | return true; 26 | } 27 | 28 | private string FormatSize(long bytes) 29 | { 30 | string[] units = { "B", "KB", "MB", "GB", "TB" }; 31 | for (int i = 0; i < units.Length; i++) 32 | { 33 | long max = (long)Math.Pow(1024, i + 1); 34 | if (bytes < max) 35 | return (int)(bytes / Math.Pow(1024, i)) + " " + units[i]; 36 | } 37 | 38 | return bytes + " B"; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /LineOS/CLI/ConsoleManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace LineOS.CLI 4 | { 5 | public class ConsoleManager 6 | { 7 | public string CurrentPath { get; set; } = "0:\\"; 8 | public CommandHandler CommandHandler { get; } = new CommandHandler(); 9 | 10 | public void HandleConsole() 11 | { 12 | WritePrompt(); 13 | 14 | var input = Console.ReadLine(); 15 | if (string.IsNullOrEmpty(input)) 16 | return; 17 | 18 | try 19 | { 20 | CommandHandler.HandleCommand(input); 21 | } 22 | catch (Exception e) 23 | { 24 | WriteError(e); 25 | } 26 | 27 | Console.WriteLine(); 28 | } 29 | 30 | private void WriteError(Exception e) 31 | { 32 | Console.ForegroundColor = ConsoleColor.White; 33 | Console.BackgroundColor = ConsoleColor.Blue; 34 | Console.WriteLine("Internal system error"); 35 | Console.WriteLine(e.ToString()); 36 | Console.ResetColor(); 37 | } 38 | 39 | private void WritePrompt() 40 | { 41 | Console.ForegroundColor = ConsoleColor.Blue; 42 | Console.Write("$"); 43 | Console.ForegroundColor = ConsoleColor.Green; 44 | Console.Write("root"); 45 | Console.ForegroundColor = ConsoleColor.DarkGray; 46 | Console.Write("@"); 47 | Console.ForegroundColor = ConsoleColor.Blue; 48 | Console.Write("lineos"); 49 | Console.ResetColor(); 50 | Console.Write(" " + CurrentPath + "> "); 51 | } 52 | 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /LineOS/CLI/ICommand.cs: -------------------------------------------------------------------------------- 1 | namespace LineOS.CLI 2 | { 3 | public interface ICommand 4 | { 5 | string Name { get; } 6 | string Description { get; } 7 | string Syntax { get; } 8 | 9 | bool Execute(string[] args); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /LineOS/CLI/TablePrinter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace LineOS.CLI 4 | { 5 | public class TablePrinter 6 | { 7 | private int[] offsets; 8 | 9 | private const string Space = " "; 10 | 11 | public void WriteHeaders(params string[] headers) 12 | { 13 | offsets = new int[headers.Length]; 14 | var offset = 4; 15 | 16 | Console.Write(Space); 17 | for (var i = 0; i < headers.Length; i++) 18 | { 19 | var header = headers[i]; 20 | offsets[i] = offset; 21 | 22 | var content = header + Space; 23 | Console.Write(content); 24 | offset += content.Length; 25 | } 26 | Console.WriteLine(); 27 | 28 | foreach (var header in headers) 29 | Console.Write(Space + new string('=', header.Length)); 30 | Console.WriteLine(); 31 | } 32 | 33 | public void WriteRow(params string[] cols) 34 | { 35 | var textLength = 0; 36 | for(var i = 0; i < cols.Length; i++) 37 | { 38 | var col = cols[i]; 39 | var off = new string(' ', offsets[i] - textLength); 40 | Console.Write(off); 41 | Console.Write(col); 42 | textLength += col.Length + off.Length; 43 | } 44 | Console.WriteLine(); 45 | } 46 | 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /LineOS/FS/BlockDeviceStream.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using Cosmos.HAL.BlockDevice; 4 | 5 | namespace LineOS.FS 6 | { 7 | public class BlockDeviceStream : Stream 8 | { 9 | private Partition blockDevice; 10 | 11 | private long BlockSize => (long)blockDevice.BlockSize; 12 | 13 | public override bool CanRead => true; 14 | public override bool CanSeek => true; 15 | public override bool CanWrite => true; 16 | public override long Length { get; } 17 | public override long Position { get; set; } 18 | 19 | private ulong currentBlockId; 20 | private ulong currentBlockOffset; 21 | private byte[] currentBlock; 22 | 23 | public BlockDeviceStream(Partition blockDevice, long length) 24 | { 25 | this.blockDevice = blockDevice; 26 | Length = length; 27 | currentBlock = blockDevice.NewBlockArray(1); 28 | } 29 | 30 | public override int Read(byte[] buffer, int offset, int count) 31 | { 32 | int read = 0; 33 | while (read < count) 34 | { 35 | buffer[offset + read] = currentBlock[currentBlockOffset]; 36 | currentBlockOffset++; 37 | if (currentBlockOffset >= (ulong)currentBlock.Length) 38 | { 39 | currentBlockId++; 40 | currentBlockOffset = 0; 41 | LoadBlock(); 42 | } 43 | read++; 44 | Position++; 45 | } 46 | return read; 47 | } 48 | 49 | public override long Seek(long offset, SeekOrigin origin) 50 | { 51 | switch (origin) 52 | { 53 | case SeekOrigin.Begin: 54 | Position = offset; 55 | break; 56 | case SeekOrigin.Current: 57 | Position += offset; 58 | break; 59 | case SeekOrigin.End: 60 | Position = Length - 1 - offset; 61 | break; 62 | default: 63 | throw new ArgumentException("origin"); 64 | } 65 | currentBlockId = (ulong)Math.Floor((double)(Position / BlockSize)); 66 | currentBlockOffset = (ulong)(Position % BlockSize); 67 | LoadBlock(); 68 | return Position; 69 | } 70 | 71 | private void LoadBlock() 72 | { 73 | blockDevice.ReadBlock(currentBlockId, 1, currentBlock); 74 | } 75 | 76 | public override void Write(byte[] buffer, int offset, int count) 77 | { 78 | throw new NotImplementedException(); 79 | } 80 | 81 | public override void Flush() 82 | { 83 | throw new NotImplementedException(); 84 | } 85 | 86 | public override void SetLength(long value) 87 | { 88 | throw new Exception("Unable to SetLength on BLOCK_DEVICE_STREAM"); 89 | } 90 | 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /LineOS/FS/NtfsAwareCosmosVFS.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using Cosmos.HAL.BlockDevice; 5 | using Cosmos.System.FileSystem; 6 | using Cosmos.System.FileSystem.Listing; 7 | using Cosmos.System.FileSystem.VFS; 8 | using LineOS.CLI; 9 | using LineOS.NTFS.Cosmos; 10 | 11 | namespace LineOS.FS 12 | { 13 | public class NtfsAwareCosmosVfs : VFSBase 14 | { 15 | private List mPartitions; 16 | private List mFileSystems; 17 | private FileSystem mCurrentFileSystem; 18 | private List mRegisteredFileSystems; 19 | private TablePrinter tablePrinter; 20 | 21 | /// 22 | /// Initializes the virtual file system. 23 | /// 24 | public override void Initialize() 25 | { 26 | mPartitions = new List(); 27 | mFileSystems = new List(); 28 | mRegisteredFileSystems = new List(); 29 | tablePrinter = new TablePrinter(); 30 | 31 | RegisterFileSystem(new NtfsFileSystemFactory()); 32 | RegisterFileSystem(new FatFileSystemFactory()); 33 | 34 | InitializePartitions(); 35 | if (mPartitions.Count > 0) 36 | { 37 | InitializeFileSystems(); 38 | } 39 | } 40 | 41 | public override void RegisterFileSystem(FileSystemFactory aFileSystemFactory) 42 | { 43 | mRegisteredFileSystems.Add(aFileSystemFactory); 44 | } 45 | 46 | /// 47 | /// Creates a new file. 48 | /// 49 | /// The full path including the file to create. 50 | /// 51 | /// 52 | /// aPath 53 | public override DirectoryEntry CreateFile(string aPath) 54 | { 55 | 56 | if (aPath == null) 57 | { 58 | throw new ArgumentNullException(nameof(aPath)); 59 | } 60 | 61 | if (aPath.Length == 0) 62 | { 63 | throw new ArgumentException("aPath"); 64 | } 65 | 66 | 67 | if (File.Exists(aPath)) 68 | { 69 | return GetFile(aPath); 70 | } 71 | 72 | string xFileToCreate = Path.GetFileName(aPath); 73 | 74 | string xParentDirectory = Path.GetDirectoryName(aPath); 75 | 76 | DirectoryEntry xParentEntry = GetDirectory(xParentDirectory); 77 | if (xParentEntry == null) 78 | { 79 | xParentEntry = CreateDirectory(xParentDirectory); 80 | } 81 | 82 | var xFS = GetFileSystemFromPath(xParentDirectory); 83 | return xFS.CreateFile(xParentEntry, xFileToCreate); 84 | } 85 | 86 | /// 87 | /// Creates a directory. 88 | /// 89 | /// The full path including the directory to create. 90 | /// 91 | /// 92 | /// aPath 93 | public override DirectoryEntry CreateDirectory(string aPath) 94 | { 95 | 96 | if (aPath == null) 97 | { 98 | throw new ArgumentNullException(nameof(aPath)); 99 | } 100 | 101 | if (aPath.Length == 0) 102 | { 103 | throw new ArgumentException("aPath"); 104 | } 105 | 106 | 107 | if (Directory.Exists(aPath)) 108 | { 109 | return GetDirectory(aPath); 110 | } 111 | 112 | 113 | aPath = aPath.TrimEnd(DirectorySeparatorChar, AltDirectorySeparatorChar); 114 | 115 | string xDirectoryToCreate = Path.GetFileName(aPath); 116 | 117 | string xParentDirectory = Path.GetDirectoryName(aPath); 118 | 119 | DirectoryEntry xParentEntry = GetDirectory(xParentDirectory); 120 | 121 | if (xParentEntry == null) 122 | { 123 | xParentEntry = CreateDirectory(xParentDirectory); 124 | } 125 | 126 | 127 | var xFS = GetFileSystemFromPath(xParentDirectory); 128 | return xFS.CreateDirectory(xParentEntry, xDirectoryToCreate); 129 | } 130 | 131 | /// 132 | /// Deletes a file. 133 | /// 134 | /// The full path. 135 | /// 136 | public override bool DeleteFile(DirectoryEntry aPath) 137 | { 138 | try 139 | { 140 | var xFS = GetFileSystemFromPath(aPath.mFullPath); 141 | xFS.DeleteFile(aPath); 142 | return true; 143 | } 144 | catch 145 | { 146 | return false; 147 | } 148 | } 149 | 150 | /// 151 | /// Deletes an empty directory. 152 | /// 153 | /// The full path. 154 | /// 155 | public override bool DeleteDirectory(DirectoryEntry aPath) 156 | { 157 | try 158 | { 159 | if (GetDirectoryListing(aPath).Count > 0) 160 | { 161 | throw new Exception("Directory is not empty"); 162 | } 163 | 164 | var xFS = GetFileSystemFromPath(aPath.mFullPath); 165 | xFS.DeleteDirectory(aPath); 166 | return true; 167 | } 168 | catch 169 | { 170 | return false; 171 | } 172 | } 173 | 174 | /// 175 | /// Gets the directory listing for a path. 176 | /// 177 | /// The full path. 178 | /// 179 | public override List GetDirectoryListing(string aPath) 180 | { 181 | var xFS = GetFileSystemFromPath(aPath); 182 | var xDirectory = DoGetDirectoryEntry(aPath, xFS); 183 | return xFS.GetDirectoryListing(xDirectory); 184 | } 185 | 186 | /// 187 | /// Gets the directory listing for a directory entry. 188 | /// 189 | /// The directory entry. 190 | /// 191 | public override List GetDirectoryListing(DirectoryEntry aDirectory) 192 | { 193 | if (aDirectory == null || String.IsNullOrEmpty(aDirectory.mFullPath)) 194 | { 195 | throw new ArgumentException("Argument is null or empty", nameof(aDirectory)); 196 | } 197 | 198 | return GetDirectoryListing(aDirectory.mFullPath); 199 | } 200 | 201 | /// 202 | /// Gets the directory entry for a directory. 203 | /// 204 | /// The full path path. 205 | /// A directory entry for the directory. 206 | /// 207 | public override DirectoryEntry GetDirectory(string aPath) 208 | { 209 | try 210 | { 211 | var xFileSystem = GetFileSystemFromPath(aPath); 212 | var xEntry = DoGetDirectoryEntry(aPath, xFileSystem); 213 | if ((xEntry != null) && (xEntry.mEntryType == DirectoryEntryTypeEnum.Directory)) 214 | { 215 | return xEntry; 216 | } 217 | } 218 | catch (Exception) 219 | { 220 | return null; 221 | } 222 | throw new Exception(aPath + " was found, but is not a directory."); 223 | } 224 | 225 | /// 226 | /// Gets the directory entry for a file. 227 | /// 228 | /// The full path. 229 | /// A directory entry for the file. 230 | /// 231 | public override DirectoryEntry GetFile(string aPath) 232 | { 233 | try 234 | { 235 | var xFileSystem = GetFileSystemFromPath(aPath); 236 | var xEntry = DoGetDirectoryEntry(aPath, xFileSystem); 237 | if ((xEntry != null) && (xEntry.mEntryType == DirectoryEntryTypeEnum.File)) 238 | { 239 | return xEntry; 240 | } 241 | } 242 | catch (Exception) 243 | { 244 | return null; 245 | } 246 | throw new Exception(aPath + " was found, but is not a file."); 247 | } 248 | 249 | /// 250 | /// Gets the volumes for all registered file systems. 251 | /// 252 | /// A list of directory entries for all volumes. 253 | public override List GetVolumes() 254 | { 255 | List xVolumes = new List(); 256 | 257 | for (int i = 0; i < mFileSystems.Count; i++) 258 | { 259 | xVolumes.Add(GetVolume(mFileSystems[i])); 260 | } 261 | 262 | return xVolumes; 263 | } 264 | 265 | /// 266 | /// Gets the directory entry for a volume. 267 | /// 268 | /// The volume root path. 269 | /// A directory entry for the volume. 270 | public override DirectoryEntry GetVolume(string aPath) 271 | { 272 | if (string.IsNullOrEmpty(aPath)) 273 | { 274 | return null; 275 | } 276 | 277 | var xFileSystem = GetFileSystemFromPath(aPath); 278 | if (xFileSystem != null) 279 | { 280 | return GetVolume(xFileSystem); 281 | } 282 | 283 | return null; 284 | } 285 | 286 | /// 287 | /// Gets the attributes for a File / Directory. 288 | /// 289 | /// The path of the File / Directory. 290 | /// The File / Directory attributes. 291 | public override FileAttributes GetFileAttributes(string aPath) 292 | { 293 | /* 294 | * We are limiting ourselves to the simpler attributes File and Directory for now. 295 | * I think that in the end FAT does not support anything else 296 | */ 297 | 298 | var xFileSystem = GetFileSystemFromPath(aPath); 299 | var xEntry = DoGetDirectoryEntry(aPath, xFileSystem); 300 | 301 | if (xEntry == null) 302 | throw new Exception($"{aPath} is neither a file neither a directory"); 303 | 304 | switch (xEntry.mEntryType) 305 | { 306 | case DirectoryEntryTypeEnum.File: 307 | return FileAttributes.Normal; 308 | 309 | case DirectoryEntryTypeEnum.Directory: 310 | return FileAttributes.Directory; 311 | 312 | case DirectoryEntryTypeEnum.Unknown: 313 | default: 314 | throw new Exception($"{aPath} is neither a file neither a directory"); 315 | } 316 | } 317 | 318 | /// 319 | /// Sets the attributes for a File / Directory. 320 | /// 321 | /// The path of the File / Directory. 322 | /// The attributes of the File / Directory. 323 | public override void SetFileAttributes(string aPath, FileAttributes fileAttributes) 324 | { 325 | throw new NotImplementedException("SetFileAttributes not implemented"); 326 | } 327 | 328 | /// 329 | /// Initializes the partitions for all block devices. 330 | /// 331 | protected virtual void InitializePartitions() 332 | { 333 | Console.WriteLine("Loading partitions..."); 334 | foreach (var t in BlockDevice.Devices) 335 | if (t is Partition partition) 336 | mPartitions.Add(partition); 337 | 338 | if (mPartitions.Count > 0) 339 | { 340 | tablePrinter.WriteHeaders("Partition #", "Block Size", "Block Count", "Size"); 341 | for (int i = 0; i < mPartitions.Count; i++) 342 | { 343 | tablePrinter.WriteRow((i + 1).ToString(), mPartitions[i].BlockSize + " bytes", mPartitions[i].BlockCount.ToString(), mPartitions[i].BlockCount * mPartitions[i].BlockSize / 1024 / 1024 + " MB"); 344 | } 345 | } 346 | else 347 | { 348 | Console.WriteLine("No partitions found!"); 349 | } 350 | } 351 | 352 | /// 353 | /// Initializes the file system for all partitions. 354 | /// 355 | protected virtual void InitializeFileSystems() 356 | { 357 | for (int i = 0; i < mPartitions.Count; i++) 358 | { 359 | string rootPath = string.Concat(i, VolumeSeparatorChar, DirectorySeparatorChar); 360 | var size = (long)(mPartitions[i].BlockCount * mPartitions[i].BlockSize / 1024 / 1024); 361 | 362 | Console.WriteLine("Searching file system on partition #" + i); 363 | 364 | foreach (var fs in mRegisteredFileSystems) 365 | if (fs.IsType(mPartitions[i])) 366 | { 367 | mFileSystems.Add(fs.Create(mPartitions[i], rootPath, size)); 368 | break; 369 | } 370 | 371 | if (mFileSystems.Count > 0 && mFileSystems[mFileSystems.Count - 1].RootPath == rootPath) 372 | { 373 | Console.WriteLine(string.Concat("Initialized ", mFileSystems.Count, " filesystem(s)")); 374 | Directory.SetCurrentDirectory(rootPath); 375 | } 376 | else 377 | { 378 | Console.WriteLine(string.Concat("No filesystem found on partition #", i)); 379 | } 380 | } 381 | } 382 | 383 | /// 384 | /// Gets the file system from a path. 385 | /// 386 | /// The path. 387 | /// The file system for the path. 388 | /// Unable to determine filesystem for path: + aPath 389 | private FileSystem GetFileSystemFromPath(string aPath) 390 | { 391 | 392 | if (String.IsNullOrEmpty(aPath)) 393 | { 394 | throw new ArgumentException("Argument is null or empty", nameof(aPath)); 395 | } 396 | 397 | 398 | string xPath = Path.GetPathRoot(aPath); 399 | 400 | if ((mCurrentFileSystem != null) && (xPath == mCurrentFileSystem.RootPath)) 401 | { 402 | return mCurrentFileSystem; 403 | } 404 | 405 | for (int i = 0; i < mFileSystems.Count; i++) 406 | { 407 | if (mFileSystems[i].RootPath == xPath) 408 | { 409 | mCurrentFileSystem = mFileSystems[i]; 410 | return mCurrentFileSystem; 411 | } 412 | } 413 | throw new Exception("Unable to determine filesystem for path: " + aPath); 414 | } 415 | 416 | /// 417 | /// Attempts to get a directory entry for a path in a file system. 418 | /// 419 | /// The path. 420 | /// The file system. 421 | /// A directory entry for the path. 422 | /// aFS 423 | /// Path part ' + xPathPart + ' not found! 424 | private DirectoryEntry DoGetDirectoryEntry(string aPath, FileSystem aFS) 425 | { 426 | 427 | if (String.IsNullOrEmpty(aPath)) 428 | { 429 | throw new ArgumentException("Argument is null or empty", nameof(aPath)); 430 | } 431 | 432 | if (aFS == null) 433 | { 434 | throw new ArgumentNullException(nameof(aFS)); 435 | } 436 | 437 | 438 | string[] xPathParts = VFSManager.SplitPath(aPath); 439 | 440 | DirectoryEntry xBaseDirectory = GetVolume(aFS); 441 | 442 | if (xPathParts.Length == 1) 443 | { 444 | return xBaseDirectory; 445 | } 446 | 447 | // start at index 1, because 0 is the volume 448 | for (int i = 1; i < xPathParts.Length; i++) 449 | { 450 | var xPathPart = xPathParts[i].ToLower(); 451 | 452 | var xPartFound = false; 453 | var xListing = aFS.GetDirectoryListing(xBaseDirectory); 454 | 455 | for (int j = 0; j < xListing.Count; j++) 456 | { 457 | var xListingItem = xListing[j]; 458 | string xListingItemName = xListingItem.mName.ToLower(); 459 | xPathPart = xPathPart.ToLower(); 460 | 461 | if (xListingItemName == xPathPart) 462 | { 463 | xBaseDirectory = xListingItem; 464 | xPartFound = true; 465 | break; 466 | } 467 | } 468 | 469 | if (!xPartFound) 470 | { 471 | throw new Exception("Path part '" + xPathPart + "' not found!"); 472 | } 473 | } 474 | return xBaseDirectory; 475 | } 476 | 477 | /// 478 | /// Gets the root directory entry for a volume in a file system. 479 | /// 480 | /// The file system containing the volume. 481 | /// A directory entry for the volume. 482 | private DirectoryEntry GetVolume(FileSystem aFS) 483 | { 484 | 485 | if (aFS == null) 486 | { 487 | throw new ArgumentNullException(nameof(aFS)); 488 | } 489 | 490 | return aFS.GetRootDirectory(); 491 | } 492 | 493 | /// 494 | /// Verifies if driveId is a valid id for a drive. 495 | /// 496 | /// The id of the drive. 497 | /// true if the drive id is valid, false otherwise. 498 | public override bool IsValidDriveId(string driveId) 499 | { 500 | 501 | /* We need to remove ':\' to get only the numeric value */ 502 | driveId = driveId.Remove(driveId.Length - 2); 503 | 504 | /* 505 | * Cosmos Drive name is really similar to DOS / Windows but a number instead of a letter is used, it is not limited 506 | * to 1 character but any number is valid 507 | */ 508 | 509 | bool isOK = Int32.TryParse(driveId, out int val); 510 | 511 | return isOK; 512 | } 513 | 514 | public override long GetTotalSize(string aDriveId) 515 | { 516 | var xFs = GetFileSystemFromPath(aDriveId); 517 | 518 | /* We have to return it in bytes */ 519 | return xFs.Size * 1024 * 1024; 520 | } 521 | 522 | public override long GetAvailableFreeSpace(string aDriveId) 523 | { 524 | var xFs = GetFileSystemFromPath(aDriveId); 525 | 526 | return xFs.AvailableFreeSpace; 527 | } 528 | 529 | public override long GetTotalFreeSpace(string aDriveId) 530 | { 531 | var xFs = GetFileSystemFromPath(aDriveId); 532 | 533 | return xFs.TotalFreeSpace; 534 | } 535 | 536 | public override string GetFileSystemType(string aDriveId) 537 | { 538 | var xFs = GetFileSystemFromPath(aDriveId); 539 | 540 | return xFs.Type; 541 | } 542 | 543 | public override string GetFileSystemLabel(string aDriveId) 544 | { 545 | var xFs = GetFileSystemFromPath(aDriveId); 546 | 547 | return xFs.Label; 548 | } 549 | 550 | public override void SetFileSystemLabel(string aDriveId, string aLabel) 551 | { 552 | 553 | var xFs = GetFileSystemFromPath(aDriveId); 554 | xFs.Label = aLabel; 555 | } 556 | 557 | public override void Format(string aDriveId, string aDriveFormat, bool aQuick) 558 | { 559 | var xFs = GetFileSystemFromPath(aDriveId); 560 | 561 | xFs.Format(aDriveFormat, aQuick); 562 | } 563 | 564 | } 565 | } 566 | -------------------------------------------------------------------------------- /LineOS/Kernel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Cosmos.System.FileSystem.VFS; 3 | using LineOS.CLI; 4 | using LineOS.FS; 5 | using Sys = Cosmos.System; 6 | 7 | namespace LineOS 8 | { 9 | public class Kernel : Sys.Kernel 10 | { 11 | public static VFSBase FileSystem { get; private set; } 12 | public static ConsoleManager ConsoleManager { get; private set; } 13 | public static TablePrinter TablePrinter { get; private set; } 14 | 15 | protected override void BeforeRun() 16 | { 17 | FileSystem = new NtfsAwareCosmosVfs(); 18 | ConsoleManager = new ConsoleManager(); 19 | TablePrinter = new TablePrinter(); 20 | 21 | VFSManager.RegisterVFS(FileSystem); 22 | 23 | Console.Clear(); 24 | 25 | Console.WriteLine("LineOS 1.0.0"); 26 | Console.WriteLine("(c) 2018 Twometer Applications"); 27 | Console.WriteLine(); 28 | } 29 | 30 | protected override void Run() 31 | { 32 | ConsoleManager.HandleConsole(); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /LineOS/LineOS.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp2.0 5 | cosmos 6 | True 7 | ELF 8 | True 9 | MethodFooters 10 | ISO 11 | False 12 | Source 13 | False 14 | True 15 | MethodFooters 16 | Use VMware Player or Workstation to deploy and debug. 17 | ISO 18 | VMware 19 | True 20 | Source 21 | False 22 | Serial: COM1 23 | Pipe: Cosmos\Serial 24 | 192.168.0.8 25 | False 26 | False 27 | True 28 | MethodFooters 29 | Makes a USB device such as a flash drive or external hard disk bootable. 30 | USB 31 | None 32 | False 33 | Source 34 | False 35 | Serial: COM1 36 | Pipe: Cosmos\Serial 37 | 192.168.0.8 38 | False 39 | False 40 | True 41 | MethodFooters 42 | Creates a PXE setup and hosts a DCHP and TFTP server to deploy directly to physical hardware. Allows debugging with a serial cable. 43 | PXE 44 | None 45 | False 46 | Source 47 | False 48 | Serial: COM1 49 | Pipe: Cosmos\Serial 50 | 192.168.0.8 51 | False 52 | False 53 | True 54 | MethodFooters 55 | Creates a bootable ISO image which can be burned to a DVD. After running the selected project, an explorer window will open containing the ISO file. The ISO file can then be burned to a CD or DVD and used to boot a physical or virtual system. 56 | ISO 57 | None 58 | False 59 | Source 60 | False 61 | Serial: COM1 62 | Pipe: Cosmos\Serial 63 | 192.168.0.8 64 | False 65 | False 66 | ISO User 001 67 | False 68 | False 69 | 192.168.0.8 70 | Serial: COM1 71 | False 72 | Source 73 | False 74 | HyperV 75 | ISO 76 | Use Hyper-V to deploy and debug. 77 | ISO User 001 78 | MethodFooters 79 | True 80 | ISO User 001 81 | Pipe: CosmosSerial 82 | ISO User 001 83 | 84 | 85 | 86 | False 87 | False 88 | Pipe: Cosmos\Serial 89 | Serial: COM1 90 | None 91 | ISO 92 | Creates a bootable ISO image which can be burned to a DVD. After running the selected project, an explorer window will open containing the ISO file. The ISO file can then be burned to a CD or DVD and used to boot a physical or virtual system. 93 | 192.168.0.8 94 | 95 | 96 | 97 | False 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /LineOS/NTFS/Compression/CompressionResult.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2008-2011, Kenneth Bell 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a 5 | // copy of this software and associated documentation files (the "Software"), 6 | // to deal in the Software without restriction, including without limitation 7 | // the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | // and/or sell copies of the Software, and to permit persons to whom the 9 | // Software is furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in 12 | // all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | // DEALINGS IN THE SOFTWARE. 21 | // 22 | 23 | 24 | namespace LineOS.NTFS.Compression 25 | { 26 | /// 27 | /// Possible results of attempting to compress data. 28 | /// 29 | /// 30 | /// A compression routine may return Compressed, even if the data 31 | /// was 'all zeros' or increased in size. The AllZeros and Incompressible 32 | /// values are for algorithms that include special detection for these cases. 33 | /// 34 | public enum CompressionResult 35 | { 36 | /// 37 | /// The data compressed succesfully. 38 | /// 39 | Compressed, 40 | 41 | /// 42 | /// The data was all-zero's. 43 | /// 44 | AllZeros, 45 | 46 | /// 47 | /// The data was incompressible (could not fit into destination buffer). 48 | /// 49 | Incompressible 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /LineOS/NTFS/Compression/LZNT1.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2008-2011, Kenneth Bell 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a 5 | // copy of this software and associated documentation files (the "Software"), 6 | // to deal in the Software without restriction, including without limitation 7 | // the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | // and/or sell copies of the Software, and to permit persons to whom the 9 | // Software is furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in 12 | // all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | // DEALINGS IN THE SOFTWARE. 21 | // 22 | 23 | // 24 | // Contributions by bsobel: 25 | // - Compression algorithm distantly derived from Puyo tools (BSD license)* 26 | // - Decompression adjusted to support variety of block sizes 27 | // 28 | // (*) Puyo tools implements a different LZ-style algorithm 29 | // 30 | 31 | using System; 32 | 33 | namespace LineOS.NTFS.Compression 34 | { 35 | /// 36 | /// Implementation of the LZNT1 algorithm used for compressing NTFS files. 37 | /// 38 | /// 39 | /// Due to apparent bugs in Window's LZNT1 decompressor, it is strongly recommended that 40 | /// only the block size of 4096 is used. Other block sizes corrupt data on decompression. 41 | /// 42 | internal sealed class LZNT1 43 | { 44 | private const ushort SubBlockIsCompressedFlag = 0x8000; 45 | private const ushort SubBlockSizeMask = 0x0fff; 46 | 47 | // LZNT1 appears to ignore the actual block size requested, most likely due to 48 | // a bug in the decompressor, which assumes 4KB block size. To be bug-compatible, 49 | // we assume each block is 4KB on decode also. 50 | private const int FixedBlockSize = 0x1000; 51 | 52 | private static byte[] s_compressionBits = CalcCompressionBits(); 53 | 54 | public int BlockSize { get; internal set; } 55 | 56 | public LZNT1() 57 | { 58 | BlockSize = 4096; 59 | } 60 | 61 | public CompressionResult Compress(byte[] source, int sourceOffset, int sourceLength, byte[] compressed, int compressedOffset, ref int compressedLength) 62 | { 63 | uint sourcePointer = 0; 64 | uint sourceCurrentBlock = 0; 65 | uint destPointer = 0; 66 | 67 | // Set up the Lz Compression Dictionary 68 | LzWindowDictionary lzDictionary = new LzWindowDictionary(); 69 | bool nonZeroDataFound = false; 70 | 71 | for (int subBlock = 0; subBlock < sourceLength; subBlock += BlockSize) 72 | { 73 | lzDictionary.MinMatchAmount = 3; 74 | sourceCurrentBlock = sourcePointer; 75 | 76 | uint decompressedSize = (uint)Math.Min(sourceLength - subBlock, BlockSize); 77 | uint compressedSize = 0; 78 | 79 | // Start compression 80 | uint headerPosition = destPointer; 81 | compressed[compressedOffset + destPointer] = compressed[compressedOffset + destPointer + 1] = 0; 82 | destPointer += 2; 83 | 84 | while (sourcePointer - subBlock < decompressedSize) 85 | { 86 | if (destPointer + 1 >= compressedLength) 87 | { 88 | return CompressionResult.Incompressible; 89 | } 90 | 91 | byte bitFlag = 0x0; 92 | uint flagPosition = destPointer; 93 | 94 | compressed[compressedOffset + destPointer] = bitFlag; // It will be filled in later 95 | compressedSize++; 96 | destPointer++; 97 | 98 | for (int i = 0; i < 8; i++) 99 | { 100 | int lengthBits = 16 - s_compressionBits[sourcePointer - subBlock]; 101 | ushort lengthMask = (ushort)((1 << s_compressionBits[sourcePointer - subBlock]) - 1); 102 | 103 | lzDictionary.MaxMatchAmount = Math.Min(1 << lengthBits, BlockSize - 1); 104 | 105 | int[] lzSearchMatch = lzDictionary.Search(source, sourceOffset + subBlock, (uint)(sourcePointer - subBlock), decompressedSize); 106 | if (lzSearchMatch[1] > 0) 107 | { 108 | // There is a compression match 109 | if (destPointer + 2 >= compressedLength) 110 | { 111 | return CompressionResult.Incompressible; 112 | } 113 | 114 | bitFlag |= (byte)(1 << i); 115 | 116 | int rawOffset = lzSearchMatch[0]; 117 | int rawLength = lzSearchMatch[1]; 118 | 119 | int convertedOffset = (rawOffset - 1) << lengthBits; 120 | int convertedSize = (rawLength - 3) & ((1 << lengthMask) - 1); 121 | 122 | ushort convertedData = (ushort)(convertedOffset | convertedSize); 123 | WriteBytesLittleEndian(convertedData, compressed, compressedOffset + (int)destPointer); 124 | 125 | lzDictionary.AddEntryRange(source, sourceOffset + subBlock, (int)(sourcePointer - subBlock), lzSearchMatch[1]); 126 | sourcePointer += (uint)lzSearchMatch[1]; 127 | destPointer += 2; 128 | compressedSize += 2; 129 | } 130 | else 131 | { 132 | // There wasn't a match 133 | if (destPointer + 1 >= compressedLength) 134 | { 135 | return CompressionResult.Incompressible; 136 | } 137 | 138 | bitFlag |= (byte)(0 << i); 139 | 140 | if (source[sourceOffset + sourcePointer] != 0) 141 | { 142 | nonZeroDataFound = true; 143 | } 144 | 145 | compressed[compressedOffset + destPointer] = source[sourceOffset + sourcePointer]; 146 | lzDictionary.AddEntry(source, sourceOffset + subBlock, (int)(sourcePointer - subBlock)); 147 | 148 | sourcePointer++; 149 | destPointer++; 150 | compressedSize++; 151 | } 152 | 153 | // Check for out of bounds 154 | if (sourcePointer - subBlock >= decompressedSize) 155 | { 156 | break; 157 | } 158 | } 159 | 160 | // Write the real flag. 161 | compressed[compressedOffset + flagPosition] = bitFlag; 162 | } 163 | 164 | // If compressed size >= block size just store block 165 | if (compressedSize >= BlockSize) 166 | { 167 | // Set the header to indicate non-compressed block 168 | WriteBytesLittleEndian((ushort)(0x3000 | (BlockSize - 1)), compressed, compressedOffset + (int)headerPosition); 169 | 170 | Array.Copy(source, sourceOffset + sourceCurrentBlock, compressed, compressedOffset + headerPosition + 2, BlockSize); 171 | destPointer = (uint)(headerPosition + 2 + BlockSize); 172 | 173 | // Make sure decompression stops by setting the next two bytes to null, prevents us from having to 174 | // clear the rest of the array. 175 | compressed[destPointer] = 0; 176 | compressed[destPointer + 1] = 0; 177 | } 178 | else 179 | { 180 | // Set the header to indicate compressed and the right length 181 | WriteBytesLittleEndian((ushort)(0xb000 | (compressedSize - 1)), compressed, compressedOffset + (int)headerPosition); 182 | } 183 | 184 | lzDictionary.Reset(); 185 | } 186 | 187 | if (destPointer >= sourceLength) 188 | { 189 | compressedLength = 0; 190 | return CompressionResult.Incompressible; 191 | } 192 | else if (nonZeroDataFound) 193 | { 194 | compressedLength = (int)destPointer; 195 | return CompressionResult.Compressed; 196 | } 197 | else 198 | { 199 | compressedLength = 0; 200 | return CompressionResult.AllZeros; 201 | } 202 | } 203 | 204 | public int Decompress(byte[] source, int sourceOffset, int sourceLength, byte[] decompressed, int decompressedOffset) 205 | { 206 | int sourceIdx = 0; 207 | int destIdx = 0; 208 | 209 | while (sourceIdx < sourceLength) 210 | { 211 | ushort header = ToUInt16LittleEndian(source, sourceOffset + sourceIdx); 212 | sourceIdx += 2; 213 | 214 | // Look for null-terminating sub-block header 215 | if (header == 0) 216 | { 217 | break; 218 | } 219 | 220 | if ((header & SubBlockIsCompressedFlag) == 0) 221 | { 222 | int blockSize = (header & SubBlockSizeMask) + 1; 223 | Array.Copy(source, sourceOffset + sourceIdx, decompressed, decompressedOffset + destIdx, blockSize); 224 | sourceIdx += blockSize; 225 | destIdx += blockSize; 226 | } 227 | else 228 | { 229 | // compressed 230 | int destSubBlockStart = destIdx; 231 | int srcSubBlockEnd = sourceIdx + (header & SubBlockSizeMask) + 1; 232 | while (sourceIdx < srcSubBlockEnd) 233 | { 234 | byte tag = source[sourceOffset + sourceIdx]; 235 | ++sourceIdx; 236 | 237 | for (int token = 0; token < 8; ++token) 238 | { 239 | // We might have hit the end of the sub block whilst still working though 240 | // a tag - abort if we have... 241 | if (sourceIdx >= srcSubBlockEnd) 242 | { 243 | break; 244 | } 245 | 246 | if ((tag & 1) == 0) 247 | { 248 | if (decompressedOffset + destIdx >= decompressed.Length) 249 | { 250 | return destIdx; 251 | } 252 | 253 | decompressed[decompressedOffset + destIdx] = source[sourceOffset + sourceIdx]; 254 | ++destIdx; 255 | ++sourceIdx; 256 | } 257 | else 258 | { 259 | ushort lengthBits = (ushort)(16 - s_compressionBits[destIdx - destSubBlockStart]); 260 | ushort lengthMask = (ushort)((1 << lengthBits) - 1); 261 | 262 | ushort phraseToken = ToUInt16LittleEndian(source, sourceOffset + sourceIdx); 263 | sourceIdx += 2; 264 | 265 | int destBackAddr = destIdx - (phraseToken >> lengthBits) - 1; 266 | int length = (phraseToken & lengthMask) + 3; 267 | 268 | for (int i = 0; i < length; ++i) 269 | { 270 | decompressed[decompressedOffset + destIdx++] = decompressed[decompressedOffset + destBackAddr++]; 271 | } 272 | } 273 | 274 | tag >>= 1; 275 | } 276 | } 277 | 278 | // Bug-compatible - if we decompressed less than 4KB, jump to next 4KB boundary. If 279 | // that would leave less than a 4KB remaining, abort with data decompressed so far. 280 | if (decompressedOffset + destIdx + FixedBlockSize > decompressed.Length) 281 | { 282 | return destIdx; 283 | } 284 | else if (destIdx < destSubBlockStart + FixedBlockSize) 285 | { 286 | int skip = (destSubBlockStart + FixedBlockSize) - destIdx; 287 | Array.Clear(decompressed, decompressedOffset + destIdx, skip); 288 | destIdx += skip; 289 | } 290 | } 291 | } 292 | 293 | return destIdx; 294 | } 295 | 296 | private static byte[] CalcCompressionBits() 297 | { 298 | byte[] result = new byte[4096]; 299 | byte offsetBits = 0; 300 | 301 | int y = 0x10; 302 | for (int x = 0; x < result.Length; x++) 303 | { 304 | result[x] = (byte)(4 + offsetBits); 305 | if (x == y) 306 | { 307 | y <<= 1; 308 | offsetBits++; 309 | } 310 | } 311 | 312 | return result; 313 | } 314 | 315 | public static void WriteBytesLittleEndian(ushort val, byte[] buffer, int offset) 316 | { 317 | buffer[offset] = (byte)(val & 0xFF); 318 | buffer[offset + 1] = (byte)((val >> 8) & 0xFF); 319 | } 320 | 321 | public static ushort ToUInt16LittleEndian(byte[] buffer, int offset) 322 | { 323 | return (ushort)(((buffer[offset + 1] << 8) & 0xFF00) | ((buffer[offset + 0] << 0) & 0x00FF)); 324 | } 325 | } 326 | } 327 | -------------------------------------------------------------------------------- /LineOS/NTFS/Compression/LzWindowDictionary.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2008-2011, Kenneth Bell 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a 5 | // copy of this software and associated documentation files (the "Software"), 6 | // to deal in the Software without restriction, including without limitation 7 | // the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | // and/or sell copies of the Software, and to permit persons to whom the 9 | // Software is furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in 12 | // all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | // DEALINGS IN THE SOFTWARE. 21 | // 22 | using System; 23 | using System.Collections.Generic; 24 | 25 | namespace LineOS.NTFS.Compression 26 | { 27 | internal sealed class LzWindowDictionary 28 | { 29 | /// 30 | /// Index of locations of each possible byte value within the compression window. 31 | /// 32 | private List[] _offsetList; 33 | 34 | public LzWindowDictionary() 35 | { 36 | Initalize(); 37 | 38 | // Build the index list, so Lz compression will become significantly faster 39 | _offsetList = new List[0x100]; 40 | for (int i = 0; i < _offsetList.Length; i++) 41 | { 42 | _offsetList[i] = new List(); 43 | } 44 | } 45 | 46 | public int MinMatchAmount { get; set; } 47 | 48 | public int MaxMatchAmount { get; set; } 49 | 50 | private int BlockSize { get; set; } 51 | 52 | public void Reset() 53 | { 54 | Initalize(); 55 | 56 | for (int i = 0; i < _offsetList.Length; i++) 57 | { 58 | _offsetList[i].Clear(); 59 | } 60 | } 61 | 62 | public int[] Search(byte[] decompressedData, int decompressedDataOffset, uint index, uint length) 63 | { 64 | RemoveOldEntries(decompressedData[decompressedDataOffset + index]); // Remove old entries for this index 65 | 66 | int[] match = new[] { 0, 0 }; 67 | 68 | if (index < 1 || length - index < MinMatchAmount) 69 | { 70 | // Can't find matches if there isn't enough data 71 | return match; 72 | } 73 | 74 | for (int i = 0; i < _offsetList[decompressedData[decompressedDataOffset + index]].Count; i++) 75 | { 76 | int matchStart = _offsetList[decompressedData[decompressedDataOffset + index]][i]; 77 | int matchSize = 1; 78 | 79 | if (index - matchStart > BlockSize) 80 | { 81 | break; 82 | } 83 | 84 | int maxMatchSize = (int)Math.Min(Math.Min(MaxMatchAmount, BlockSize), Math.Min(length - index, length - matchStart)); 85 | while (matchSize < maxMatchSize && decompressedData[decompressedDataOffset + index + matchSize] == decompressedData[decompressedDataOffset + matchStart + matchSize]) 86 | { 87 | matchSize++; 88 | } 89 | 90 | if (matchSize >= MinMatchAmount && matchSize > match[1]) 91 | { 92 | // This is a good match 93 | match = new[] { (int)(index - matchStart), matchSize }; 94 | 95 | if (matchSize == MaxMatchAmount) 96 | { 97 | // Don't look for more matches 98 | break; 99 | } 100 | } 101 | } 102 | 103 | // Return the real match (or the default 0:0 match). 104 | return match; 105 | } 106 | 107 | // Add entries 108 | public void AddEntry(byte[] decompressedData, int decompressedDataOffset, int index) 109 | { 110 | _offsetList[decompressedData[decompressedDataOffset + index]].Add(index); 111 | } 112 | 113 | public void AddEntryRange(byte[] decompressedData, int decompressedDataOffset, int index, int length) 114 | { 115 | for (int i = 0; i < length; i++) 116 | { 117 | AddEntry(decompressedData, decompressedDataOffset, index + i); 118 | } 119 | } 120 | 121 | private void Initalize() 122 | { 123 | MinMatchAmount = 3; 124 | MaxMatchAmount = 18; 125 | BlockSize = 4096; 126 | } 127 | 128 | private void RemoveOldEntries(byte index) 129 | { 130 | while (_offsetList[index].Count > 256) 131 | { 132 | _offsetList[index].RemoveAt(0); 133 | } 134 | } 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /LineOS/NTFS/Cosmos/NtfsDirectoryEntry.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using Cosmos.System.FileSystem; 4 | using Cosmos.System.FileSystem.Listing; 5 | using LineOS.NTFS.IO; 6 | using LineOS.NTFS.Model.Attributes; 7 | 8 | namespace LineOS.NTFS.Cosmos 9 | { 10 | public class NtfsDirectoryEntry : DirectoryEntry 11 | { 12 | public NtfsFileEntry NtfsEntry; 13 | 14 | public NtfsDirectoryEntry(FileSystem aFileSystem, DirectoryEntry aParent, string aFullPath, string aName, long aSize, DirectoryEntryTypeEnum aEntryType, NtfsFileEntry entry) : base(aFileSystem, aParent, aFullPath, aName, aSize, aEntryType) 15 | { 16 | NtfsEntry = entry; 17 | } 18 | 19 | public override void SetName(string aName) 20 | { 21 | 22 | } 23 | 24 | public override void SetSize(long aSize) 25 | { 26 | 27 | } 28 | 29 | public override Stream GetFileStream() 30 | { 31 | var frec = NtfsEntry.MFTRecord; 32 | foreach (var att in frec.Attributes) 33 | { 34 | switch (att) 35 | { 36 | case AttributeGeneric gen: 37 | return new MemoryStream(gen.Data); 38 | case AttributeData data: 39 | return new MemoryStream(data.DataBytes); 40 | } 41 | } 42 | throw new Exception("ntfs: data attribute not found"); 43 | } 44 | 45 | public override long GetUsedSpace() 46 | { 47 | return 0; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /LineOS/NTFS/Cosmos/NtfsFileSystem.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Cosmos.HAL.BlockDevice; 4 | using Cosmos.System.FileSystem; 5 | using Cosmos.System.FileSystem.Listing; 6 | using LineOS.FS; 7 | using LineOS.NTFS.IO; 8 | 9 | namespace LineOS.NTFS.Cosmos 10 | { 11 | public class NtfsFileSystem : FileSystem 12 | { 13 | private readonly Partition device; 14 | private readonly string rootPath; 15 | private readonly long size; 16 | 17 | private Ntfs ntfs; 18 | 19 | public NtfsFileSystem(Partition aDevice, string aRootPath, long aSize) : base(aDevice, aRootPath, aSize) 20 | { 21 | device = aDevice; 22 | rootPath = aRootPath; 23 | size = aSize; 24 | Initialize(); 25 | } 26 | 27 | private void Initialize() 28 | { 29 | Console.WriteLine("[NTFSDRV2] Initializing NTFS file system on drive " + rootPath); 30 | ntfs = Ntfs.Create(new BlockDeviceStream(device, size)); 31 | } 32 | 33 | public override void DisplayFileSystemInfo() 34 | { 35 | } 36 | 37 | public override List GetDirectoryListing(DirectoryEntry baseDirectory) 38 | { 39 | if (!(baseDirectory is NtfsDirectoryEntry ntfsEntry)) throw new Exception("ntfs: invalid dirlist request"); 40 | if (!(ntfsEntry.NtfsEntry is NtfsDirectory ntfsDir)) throw new Exception("ntfs: dirlist: not a directory"); 41 | var result = new List(); 42 | foreach (var f in ntfsDir.ListFiles()) 43 | { 44 | result.Add(new NtfsDirectoryEntry(this, null, baseDirectory.mFullPath + "\\" + f.Name, f.Name, 45 | 0, 46 | f is NtfsFile ? DirectoryEntryTypeEnum.File : DirectoryEntryTypeEnum.Directory, f)); 47 | } 48 | 49 | return result; 50 | } 51 | 52 | public override DirectoryEntry GetRootDirectory() 53 | { 54 | return new NtfsDirectoryEntry(this, null, "\\", rootPath, size, DirectoryEntryTypeEnum.Directory, ntfs.GetRootDirectory()); 55 | } 56 | 57 | public override DirectoryEntry CreateDirectory(DirectoryEntry aParentDirectory, string aNewDirectory) 58 | { 59 | return null; 60 | } 61 | 62 | public override DirectoryEntry CreateFile(DirectoryEntry aParentDirectory, string aNewFile) 63 | { 64 | return null; 65 | } 66 | 67 | public override void DeleteDirectory(DirectoryEntry aPath) 68 | { 69 | 70 | } 71 | 72 | public override void DeleteFile(DirectoryEntry aPath) 73 | { 74 | 75 | } 76 | 77 | public override void Format(string aDriveFormat, bool aQuick) 78 | { 79 | 80 | } 81 | 82 | public override long AvailableFreeSpace { get; } 83 | public override long TotalFreeSpace => size; 84 | public override string Type => "NTFS"; 85 | public override string Label { get; set; } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /LineOS/NTFS/Cosmos/NtfsFileSystemFactory.cs: -------------------------------------------------------------------------------- 1 | using Cosmos.HAL.BlockDevice; 2 | using Cosmos.System.FileSystem; 3 | 4 | namespace LineOS.NTFS.Cosmos 5 | { 6 | public class NtfsFileSystemFactory : FileSystemFactory 7 | { 8 | public override bool IsType(Partition aDevice) 9 | { 10 | byte[] block = aDevice.NewBlockArray(1); 11 | aDevice.ReadBlock(0, 1, block); 12 | if (block[3] == 0x4E && block[4] == 0x54 && block[5] == 0x46 && block[6] == 0x53) 13 | return true; 14 | return false; 15 | } 16 | 17 | public override FileSystem Create(Partition aDevice, string aRootPath, long aSize) 18 | { 19 | return new NtfsFileSystem(aDevice, aRootPath, aSize); 20 | } 21 | 22 | public override string Name => "NTFS"; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /LineOS/NTFS/IO/DataFragmentComparer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using LineOS.NTFS.Model; 3 | 4 | namespace LineOS.NTFS.IO 5 | { 6 | public class DataFragmentComparer : IComparer 7 | { 8 | public int Compare(DataFragment x, DataFragment y) 9 | { 10 | if (x != null && y != null) return x.StartingVCN.CompareTo(y.StartingVCN); 11 | return 0; 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /LineOS/NTFS/IO/NtfsDirectory.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using LineOS.NTFS.Model; 3 | using LineOS.NTFS.Model.Attributes; 4 | using LineOS.NTFS.Model.Enums; 5 | 6 | namespace LineOS.NTFS.IO 7 | { 8 | public class NtfsDirectory : NtfsFileEntry 9 | { 10 | private const string DirlistAttribName = "$I30"; 11 | 12 | internal NtfsDirectory(Ntfs ntfs, FileRecord record, AttributeFileName fileName) 13 | : base(ntfs, record, fileName) 14 | { 15 | } 16 | 17 | private void LoadIndex(List input, List output) 18 | { 19 | foreach (var e in input) 20 | { 21 | var fileId = e.FileRefence.FileId; 22 | if (fileId <= 11) continue; 23 | var exists = false; 24 | foreach (var o in output) 25 | if (o.MFTRecord.FileReference.FileId == e.FileRefence.FileId) 26 | { 27 | exists = true; 28 | break; 29 | } 30 | if (!exists) 31 | output.Add(CreateEntry(ntfs, e.FileRefence.FileId)); 32 | } 33 | } 34 | 35 | public List ListFiles() 36 | { 37 | var result = new List(); 38 | var largeIndex = false; 39 | 40 | foreach (var att in MFTRecord.Attributes) 41 | { 42 | if (att is AttributeIndexRoot indexRoot && att.AttributeName == DirlistAttribName) 43 | { 44 | LoadIndex(indexRoot.Entries, result); 45 | largeIndex = (indexRoot.IndexFlags & MFTIndexRootFlags.LargeIndex) != 0; 46 | } 47 | } 48 | 49 | if (!largeIndex) return result; 50 | 51 | foreach (var att in MFTRecord.Attributes) 52 | { 53 | if (att is AttributeIndexAllocation alloc && att.AttributeName == DirlistAttribName) 54 | { 55 | ntfs.ParseNonResidentAttribute(alloc); 56 | LoadIndex(alloc.Entries, result); 57 | } 58 | } 59 | 60 | return result; 61 | } 62 | 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /LineOS/NTFS/IO/NtfsDiskStream.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | using System.IO; 5 | using LineOS.NTFS.Compression; 6 | using LineOS.NTFS.Model; 7 | 8 | namespace LineOS.NTFS.IO 9 | { 10 | public class NtfsDiskStream : Stream 11 | { 12 | private LZNT1 _compressor; 13 | 14 | private readonly Stream _diskStream; 15 | private readonly bool _ownsStream; 16 | private readonly uint _bytesPrCluster; 17 | private readonly ushort _compressionClusterCount; 18 | private readonly List _fragments; 19 | private long _position; 20 | private long _length; 21 | 22 | private bool IsEof 23 | { 24 | get { return _position >= _length; } 25 | } 26 | 27 | public NtfsDiskStream(Stream diskStream, bool ownsStream, List fragments, uint bytesPrCluster, ushort compressionClusterCount, long length) 28 | { 29 | _diskStream = diskStream; 30 | _ownsStream = ownsStream; 31 | _bytesPrCluster = bytesPrCluster; 32 | _compressionClusterCount = compressionClusterCount; 33 | 34 | _fragments = Utility.Util.Sort(fragments, new DataFragmentComparer()); 35 | 36 | _length = length; 37 | _position = 0; 38 | 39 | if (compressionClusterCount != 0) 40 | { 41 | _compressor = new LZNT1(); 42 | _compressor.BlockSize = (int)_bytesPrCluster; 43 | } 44 | 45 | long vcn = 0; 46 | bool hasCompression = false; 47 | for (int i = 0; i < _fragments.Count; i++) 48 | { 49 | if (_fragments[i].IsCompressed) 50 | hasCompression = true; 51 | 52 | // Debug.Assert(_fragments[i].StartingVCN == vcn); 53 | vcn += _fragments[i].Clusters + _fragments[i].CompressedClusters; 54 | } 55 | 56 | //if (_compressionClusterCount == 0) 57 | // Debug.Assert(!hasCompression); 58 | } 59 | 60 | public override void Flush() 61 | { 62 | throw new NotImplementedException(); 63 | } 64 | 65 | public override long Seek(long offset, SeekOrigin origin) 66 | { 67 | long newPosition = offset; 68 | 69 | switch (origin) 70 | { 71 | case SeekOrigin.Begin: 72 | newPosition = offset; 73 | break; 74 | case SeekOrigin.Current: 75 | newPosition += offset; 76 | break; 77 | case SeekOrigin.End: 78 | newPosition = _length + offset; 79 | break; 80 | default: 81 | throw new ArgumentOutOfRangeException("origin"); 82 | } 83 | 84 | if (newPosition < 0 || _length < newPosition) 85 | throw new ArgumentOutOfRangeException("offset"); 86 | 87 | // Set 88 | _position = newPosition; 89 | 90 | return _position; 91 | } 92 | 93 | public override void SetLength(long value) 94 | { 95 | throw new InvalidOperationException(); 96 | } 97 | 98 | public override int Read(byte[] buffer, int offset, int count) 99 | { 100 | int totalRead = 0; 101 | 102 | // Determine fragment 103 | while (count > 0 && _position < _length) 104 | { 105 | long fragmentOffset; 106 | DataFragment fragment = FindFragment(_position, out fragmentOffset); 107 | 108 | long diskOffset = fragment.LCN * _bytesPrCluster; 109 | long fragmentLength = fragment.Clusters * _bytesPrCluster; 110 | 111 | int actualRead; 112 | if (fragment.IsCompressed) 113 | { 114 | // Read and decompress 115 | byte[] compressedData = new byte[fragmentLength]; 116 | _diskStream.Seek(diskOffset, SeekOrigin.Begin); 117 | _diskStream.Read(compressedData, 0, compressedData.Length); 118 | 119 | int decompressedLength = (int)((fragment.Clusters + fragment.CompressedClusters) * _bytesPrCluster); 120 | int toRead = (int)Math.Min(decompressedLength - fragmentOffset, Math.Min(_length - _position, count)); 121 | 122 | // Debug.Assert(decompressedLength == _compressionClusterCount * _bytesPrCluster); 123 | 124 | if (fragmentOffset == 0 && toRead == decompressedLength) 125 | { 126 | // Decompress directly (we're in the middle of a file and reading a full 16 clusters out) 127 | actualRead = _compressor.Decompress(compressedData, 0, compressedData.Length, buffer, offset); 128 | } 129 | else 130 | { 131 | // Decompress temporarily 132 | byte[] tmp = new byte[decompressedLength]; 133 | int decompressed = _compressor.Decompress(compressedData, 0, compressedData.Length, tmp, 0); 134 | 135 | toRead = Math.Min(toRead, decompressed); 136 | 137 | // Copy wanted data 138 | Array.Copy(tmp, fragmentOffset, buffer, offset, toRead); 139 | 140 | actualRead = toRead; 141 | } 142 | } 143 | else if (fragment.IsSparseFragment) 144 | { 145 | // Fill with zeroes 146 | // How much to fill? 147 | int toFill = (int)Math.Min(fragmentLength - fragmentOffset, count); 148 | 149 | Array.Clear(buffer, offset, toFill); 150 | 151 | actualRead = toFill; 152 | } 153 | else 154 | { 155 | // Read directly 156 | // How much can we read? 157 | int toRead = (int)Math.Min(fragmentLength - fragmentOffset, Math.Min(_length - _position, count)); 158 | 159 | // Read it 160 | _diskStream.Seek(diskOffset + fragmentOffset, SeekOrigin.Begin); 161 | actualRead = _diskStream.Read(buffer, offset, toRead); 162 | } 163 | 164 | // Increments 165 | count -= actualRead; 166 | offset += actualRead; 167 | 168 | _position += actualRead; 169 | 170 | totalRead += actualRead; 171 | 172 | // Check 173 | if (actualRead == 0) 174 | break; 175 | } 176 | 177 | return totalRead; 178 | } 179 | 180 | private DataFragment FindFragment(long fileIndex, out long offsetInFragment) 181 | { 182 | for (int i = 0; i < _fragments.Count; i++) 183 | { 184 | long fragmentStart = _fragments[i].StartingVCN * _bytesPrCluster; 185 | long fragmentEnd = fragmentStart + (_fragments[i].Clusters + _fragments[i].CompressedClusters) * _bytesPrCluster; 186 | 187 | if (fragmentStart <= fileIndex && fileIndex < fragmentEnd) 188 | { 189 | // Found 190 | offsetInFragment = fileIndex - fragmentStart; 191 | 192 | return _fragments[i]; 193 | } 194 | } 195 | 196 | offsetInFragment = -1; 197 | 198 | return null; 199 | } 200 | 201 | public override void Write(byte[] buffer, int offset, int count) 202 | { 203 | // Not implemented 204 | throw new InvalidOperationException(); 205 | } 206 | 207 | public override bool CanRead 208 | { 209 | get { return true; } 210 | } 211 | 212 | public override bool CanSeek 213 | { 214 | get { return true; } 215 | } 216 | 217 | public override bool CanWrite 218 | { 219 | // Not implemented 220 | get { return false; } 221 | } 222 | 223 | public override long Length 224 | { 225 | get { return _length; } 226 | } 227 | 228 | public override long Position 229 | { 230 | get { return _position; } 231 | set 232 | { 233 | if (value < 0 || _length < value) 234 | throw new ArgumentOutOfRangeException("value"); 235 | 236 | _position = value; 237 | } 238 | } 239 | 240 | protected override void Dispose(bool disposing) 241 | { 242 | base.Dispose(disposing); 243 | 244 | if (_ownsStream) 245 | _diskStream.Dispose(); 246 | } 247 | 248 | public override void Close() 249 | { 250 | base.Close(); 251 | 252 | if (_ownsStream) 253 | _diskStream.Close(); 254 | } 255 | } 256 | } 257 | -------------------------------------------------------------------------------- /LineOS/NTFS/IO/NtfsFile.cs: -------------------------------------------------------------------------------- 1 | using LineOS.NTFS.Model; 2 | using LineOS.NTFS.Model.Attributes; 3 | 4 | namespace LineOS.NTFS.IO 5 | { 6 | public class NtfsFile : NtfsFileEntry 7 | { 8 | internal NtfsFile(Ntfs ntfs, FileRecord record, AttributeFileName fileName) 9 | : base(ntfs, record, fileName) 10 | { 11 | } 12 | 13 | public override string ToString() 14 | { 15 | return FileName.FileName; 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /LineOS/NTFS/IO/NtfsFileCache.cs: -------------------------------------------------------------------------------- 1 | using LineOS.NTFS.Utility; 2 | 3 | namespace LineOS.NTFS.IO 4 | { 5 | internal class NtfsFileCache 6 | { 7 | private CompatDictionary _entries; 8 | 9 | internal NtfsFileCache() 10 | { 11 | _entries = new CompatDictionary(); 12 | } 13 | 14 | private ulong CreateKey(uint id, int filenameHashcode) 15 | { 16 | ulong key = (ulong)id << 32; 17 | 18 | if (filenameHashcode > 0) 19 | key |= (ulong)filenameHashcode; 20 | else 21 | { 22 | ulong tmp = (ulong)(-filenameHashcode); 23 | tmp += (uint)1 << 31; // the 1-bit that's normally the sign bit 24 | key |= tmp; 25 | } 26 | 27 | return key; 28 | } 29 | 30 | public NtfsFileEntry Get(uint id, int filenameHashcode) 31 | { 32 | // Make combined key 33 | ulong key = CreateKey(id, filenameHashcode); 34 | 35 | // Fetch 36 | NtfsFileEntry tmp; 37 | _entries.TryGetValue(key, out tmp); 38 | 39 | if (tmp == null) 40 | return null; 41 | 42 | return tmp; 43 | } 44 | 45 | public void Set(uint id, ushort attributeId, NtfsFileEntry entry) 46 | { 47 | // Make combined key 48 | ulong key = CreateKey(id, attributeId); 49 | 50 | // Set 51 | _entries[key] = entry; 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /LineOS/NTFS/IO/NtfsFileEntry.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using LineOS.NTFS.Model; 3 | using LineOS.NTFS.Model.Attributes; 4 | using LineOS.NTFS.Model.Enums; 5 | using LineOS.NTFS.Utility; 6 | 7 | namespace LineOS.NTFS.IO 8 | { 9 | public abstract class NtfsFileEntry 10 | { 11 | protected Ntfs ntfs; 12 | public FileRecord MFTRecord { get; private set; } 13 | 14 | internal AttributeFileName FileName; 15 | private AttributeStandardInformation _standardInformation; 16 | 17 | public DateTime TimeCreation => _standardInformation == null ? DateTime.MinValue : _standardInformation.TimeCreated; 18 | 19 | public DateTime TimeModified => _standardInformation == null ? DateTime.MinValue : _standardInformation.TimeModified; 20 | 21 | public DateTime TimeAccessed => _standardInformation == null ? DateTime.MinValue : _standardInformation.TimeAccessed; 22 | 23 | public DateTime TimeMftModified => _standardInformation == null ? DateTime.MinValue : _standardInformation.TimeMftModified; 24 | 25 | public string Name => FileName.FileName; 26 | 27 | public NtfsDirectory Parent => CreateEntry(FileName.ParentDirectory.FileId) as NtfsDirectory; 28 | 29 | protected NtfsFileEntry(Ntfs ntfs, FileRecord record, AttributeFileName fileName) 30 | { 31 | this.ntfs = ntfs; 32 | MFTRecord = record; 33 | FileName = fileName; 34 | Init(); 35 | } 36 | 37 | private void Init() 38 | { 39 | foreach(var att in MFTRecord.Attributes) 40 | if (att is AttributeStandardInformation info) 41 | _standardInformation = info; 42 | } 43 | 44 | internal NtfsFileEntry CreateEntry(uint fileId, AttributeFileName fileName = null) 45 | { 46 | return CreateEntry(ntfs, fileId, fileName); 47 | } 48 | 49 | internal static NtfsFileEntry CreateEntry(Ntfs ntfs, uint fileId, AttributeFileName fileName = null) 50 | { 51 | var record = ntfs.ReadMftRecord(fileId); 52 | if (fileName == null) 53 | fileName = NtfsUtils.GetPreferredDisplayName(record); 54 | 55 | if ((record.Flags & FileEntryFlags.Directory) != 0) 56 | return new NtfsDirectory(ntfs, record, fileName); 57 | return new NtfsFile(ntfs, record, fileName); 58 | } 59 | 60 | } 61 | } -------------------------------------------------------------------------------- /LineOS/NTFS/Model/Attributes/Attribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using LineOS.NTFS.Model.Enums; 5 | using LineOS.NTFS.Model.Headers; 6 | using LineOS.NTFS.Utility; 7 | 8 | namespace LineOS.NTFS.Model.Attributes 9 | { 10 | public abstract class Attribute : ISaveableObject 11 | { 12 | public AttributeType Type { get; set; } 13 | public ushort TotalLength { get; set; } 14 | public ResidentFlag NonResidentFlag { get; set; } 15 | public byte NameLength { get; set; } 16 | public ushort OffsetToName { get; set; } 17 | public AttributeFlags Flags { get; set; } 18 | public ushort Id { get; set; } 19 | 20 | public FileReference OwningRecord { get; set; } 21 | public string AttributeName { get; set; } 22 | 23 | public AttributeResidentHeader ResidentHeader { get; set; } 24 | public AttributeNonResidentHeader NonResidentHeader { get; set; } 25 | 26 | public abstract AttributeResidentAllow AllowedResidentStates { get; } 27 | 28 | public static AttributeType GetType(byte[] data, int offset) 29 | { 30 | // Debug.Assert(data.Length - offset >= 4); 31 | 32 | return (AttributeType)BitConverter.ToUInt32(data, offset); 33 | } 34 | 35 | public static ushort GetTotalLength(byte[] data, int offset) 36 | { 37 | // Debug.Assert(data.Length - offset + 4 >= 2); 38 | 39 | return BitConverter.ToUInt16(data, offset + 4); 40 | } 41 | 42 | private void ParseHeader(byte[] data, int offset) 43 | { 44 | // Debug.Assert(data.Length - offset >= 16); 45 | // Debug.Assert(0 <= offset && offset <= data.Length); 46 | 47 | Type = (AttributeType)BitConverter.ToUInt32(data, offset); 48 | 49 | if (Type == AttributeType.EndOfAttributes) 50 | return; 51 | 52 | TotalLength = BitConverter.ToUInt16(data, offset + 4); 53 | NonResidentFlag = (ResidentFlag)data[offset + 8]; 54 | NameLength = data[offset + 9]; 55 | OffsetToName = BitConverter.ToUInt16(data, offset + 10); 56 | Flags = (AttributeFlags)BitConverter.ToUInt16(data, offset + 12); 57 | Id = BitConverter.ToUInt16(data, offset + 14); 58 | 59 | if (NameLength == 0) 60 | AttributeName = string.Empty; 61 | else 62 | AttributeName = Encoding.Unicode.GetString(data, offset + OffsetToName, NameLength * 2); 63 | } 64 | 65 | internal virtual void ParseAttributeResidentBody(byte[] data, int maxLength, int offset) 66 | { 67 | // Debug.Assert(NonResidentFlag == ResidentFlag.Resident); 68 | // Debug.Assert((AllowedResidentStates & AttributeResidentAllow.Resident) != 0); 69 | 70 | // Debug.Assert(data.Length - offset >= maxLength); 71 | // Debug.Assert(0 <= offset && offset <= data.Length); 72 | } 73 | 74 | internal virtual void ParseAttributeNonResidentBody(Ntfs ntfsInfo) 75 | { 76 | // Debug.Assert(NonResidentFlag == ResidentFlag.NonResident); 77 | // Debug.Assert((AllowedResidentStates & AttributeResidentAllow.NonResident) != 0); 78 | // Debug.Assert(ntfsInfo != null); 79 | } 80 | 81 | public static Attribute ParseSingleAttribute(byte[] data, int maxLength, int offset = 0) 82 | { 83 | // Debug.Assert(data.Length - offset >= maxLength); 84 | // Debug.Assert(0 <= offset && offset <= data.Length); 85 | 86 | AttributeType type = GetType(data, offset); 87 | 88 | if (type == AttributeType.EndOfAttributes) 89 | { 90 | Attribute tmpRes = new AttributeGeneric(); 91 | tmpRes.ParseHeader(data, offset); 92 | 93 | return tmpRes; 94 | } 95 | 96 | Attribute res; 97 | 98 | switch (type) 99 | { 100 | case AttributeType.Unknown: 101 | res = new AttributeGeneric(); 102 | break; 103 | case AttributeType.STANDARD_INFORMATION: 104 | res = new AttributeStandardInformation(); 105 | break; 106 | case AttributeType.ATTRIBUTE_LIST: 107 | res = new AttributeList(); 108 | break; 109 | case AttributeType.FILE_NAME: 110 | res = new AttributeFileName(); 111 | break; 112 | case AttributeType.OBJECT_ID: 113 | // Also OBJECT_ID 114 | // TODO: Handle either case 115 | res = new AttributeObjectId(); 116 | break; 117 | case AttributeType.SECURITY_DESCRIPTOR: 118 | res = new AttributeSecurityDescriptor(); 119 | break; 120 | case AttributeType.VOLUME_NAME: 121 | res = new AttributeVolumeName(); 122 | break; 123 | case AttributeType.VOLUME_INFORMATION: 124 | res = new AttributeVolumeInformation(); 125 | break; 126 | case AttributeType.DATA: 127 | res = new AttributeData(); 128 | break; 129 | case AttributeType.INDEX_ROOT: 130 | res = new AttributeIndexRoot(); 131 | break; 132 | case AttributeType.INDEX_ALLOCATION: 133 | res = new AttributeIndexAllocation(); 134 | break; 135 | case AttributeType.BITMAP: 136 | res = new AttributeBitmap(); 137 | break; 138 | case AttributeType.REPARSE_POINT: 139 | // TODO 140 | res = new AttributeGeneric(); 141 | break; 142 | case AttributeType.EA_INFORMATION: 143 | res = new AttributeExtendedAttributeInformation(); 144 | break; 145 | case AttributeType.EA: 146 | res = new AttributeExtendedAttributes(); 147 | break; 148 | // Property set seems to be obsolete 149 | //case AttributeType.PROPERTY_SET: 150 | // res = new MFTAttributeGeneric(); 151 | // break; 152 | case AttributeType.LOGGED_UTILITY_STREAM: 153 | res = new AttributeLoggedUtilityStream(); 154 | break; 155 | default: 156 | // TODO 157 | res = new AttributeGeneric(); 158 | break; 159 | } 160 | 161 | res.ParseHeader(data, offset); 162 | if (res.NonResidentFlag == ResidentFlag.Resident) 163 | { 164 | // Debug.Assert((res.AllowedResidentStates & AttributeResidentAllow.Resident) != 0); 165 | 166 | res.ResidentHeader = AttributeResidentHeader.ParseHeader(data, offset + 16); 167 | 168 | int bodyOffset = offset + res.ResidentHeader.ContentOffset; 169 | int length = offset + res.TotalLength - bodyOffset; 170 | 171 | // Debug.Assert(length >= res.ResidentHeader.ContentLength); 172 | // Debug.Assert(offset + maxLength >= bodyOffset + length); 173 | 174 | res.ParseAttributeResidentBody(data, length, bodyOffset); 175 | } 176 | else if (res.NonResidentFlag == ResidentFlag.NonResident) 177 | { 178 | // Debug.Assert((res.AllowedResidentStates & AttributeResidentAllow.NonResident) != 0); 179 | 180 | res.NonResidentHeader = AttributeNonResidentHeader.ParseHeader(data, offset + 16); 181 | 182 | int bodyOffset = offset + res.NonResidentHeader.ListOffset; 183 | int length = res.TotalLength - res.NonResidentHeader.ListOffset; 184 | 185 | // Debug.Assert(offset + maxLength >= bodyOffset + length); 186 | 187 | res.NonResidentHeader.Fragments = DataFragment.ParseFragments(data, length, bodyOffset, res.NonResidentHeader.StartingVCN, res.NonResidentHeader.EndingVCN); 188 | 189 | // Compact compressed fragments 190 | if (res.NonResidentHeader.CompressionUnitSize != 0) 191 | { 192 | List fragments = res.NonResidentHeader.Fragments; 193 | DataFragment.CompactCompressedFragments(fragments); 194 | res.NonResidentHeader.Fragments = fragments; 195 | } 196 | } 197 | else 198 | { 199 | throw new NotImplementedException("Couldn't process residentflag"); 200 | } 201 | 202 | return res; 203 | } 204 | 205 | public virtual int GetSaveLength() 206 | { 207 | throw new NotImplementedException(); 208 | 209 | if (Type == AttributeType.EndOfAttributes) 210 | return 4; 211 | 212 | int length = 16 + NameLength * 2; 213 | 214 | if (NonResidentFlag == ResidentFlag.NonResident) 215 | { 216 | length += NonResidentHeader.GetSaveLength(); 217 | } 218 | else if (NonResidentFlag == ResidentFlag.Resident) 219 | { 220 | length += ResidentHeader.GetSaveLength(); 221 | } 222 | 223 | return length; 224 | } 225 | 226 | public virtual void Save(byte[] buffer, int offset) 227 | { 228 | throw new NotImplementedException(); 229 | 230 | // Debug.Assert(buffer.Length - offset >= GetSaveLength()); 231 | // Debug.Assert(offset >= 0); 232 | 233 | LittleEndianConverter.GetBytes(buffer, offset, (uint)Type); 234 | 235 | if (Type == AttributeType.EndOfAttributes) 236 | return; 237 | 238 | LittleEndianConverter.GetBytes(buffer, offset + 4, TotalLength); 239 | LittleEndianConverter.GetBytes(buffer, offset + 8, (byte)NonResidentFlag); 240 | LittleEndianConverter.GetBytes(buffer, offset + 9, NameLength); 241 | LittleEndianConverter.GetBytes(buffer, offset + 10, OffsetToName); 242 | LittleEndianConverter.GetBytes(buffer, offset + 12, (ushort)Flags); 243 | LittleEndianConverter.GetBytes(buffer, offset + 14, Id); 244 | 245 | if (NameLength != 0) 246 | { 247 | byte[] stringData = Encoding.Unicode.GetBytes(AttributeName); 248 | 249 | // Debug.Assert(NameLength * 2 == stringData.Length); 250 | 251 | Array.Copy(stringData, 0, buffer, offset + OffsetToName, stringData.Length); 252 | } 253 | 254 | // Header 255 | if (NonResidentFlag == ResidentFlag.NonResident) 256 | { 257 | NonResidentHeader.Save(buffer, offset + 16); 258 | } 259 | else if (NonResidentFlag == ResidentFlag.Resident) 260 | { 261 | ResidentHeader.Save(buffer, offset + 16); 262 | } 263 | } 264 | } 265 | } -------------------------------------------------------------------------------- /LineOS/NTFS/Model/Attributes/AttributeBitmap.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using LineOS.NTFS.Model.Enums; 4 | using LineOS.NTFS.Utility; 5 | 6 | namespace LineOS.NTFS.Model.Attributes 7 | { 8 | public class AttributeBitmap : Attribute 9 | { 10 | public BitArray Bitfield { get; set; } 11 | 12 | public override AttributeResidentAllow AllowedResidentStates 13 | { 14 | get 15 | { 16 | return AttributeResidentAllow.Resident | AttributeResidentAllow.NonResident; 17 | } 18 | } 19 | 20 | internal override void ParseAttributeResidentBody(byte[] data, int maxLength, int offset) 21 | { 22 | base.ParseAttributeResidentBody(data, maxLength, offset); 23 | 24 | // Debug.Assert(maxLength >= 1); 25 | 26 | byte[] tmpData = new byte[maxLength]; 27 | Array.Copy(data, offset, tmpData, 0, maxLength); 28 | 29 | Bitfield = new BitArray(tmpData); 30 | } 31 | 32 | internal override void ParseAttributeNonResidentBody(Ntfs ntfsInfo) 33 | { 34 | base.ParseAttributeNonResidentBody(ntfsInfo); 35 | 36 | // Get all chunks 37 | byte[] data = NtfsUtils.ReadFragments(ntfsInfo, NonResidentHeader.Fragments); 38 | 39 | // Parse 40 | Bitfield = new BitArray(data); 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /LineOS/NTFS/Model/Attributes/AttributeData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using LineOS.NTFS.Model.Enums; 4 | 5 | namespace LineOS.NTFS.Model.Attributes 6 | { 7 | public class AttributeData : Attribute 8 | { 9 | /// 10 | /// If NonResidentFlag == ResidentFlag.Resident, then DataBytes has all the data of the entry 11 | /// 12 | public byte[] DataBytes { get; set; } 13 | 14 | /// 15 | /// If NonResidentFlag == ResidentFlag.NonResident, then the DataFragments property describes all data fragments 16 | /// 17 | public List DataFragments 18 | { 19 | get { return NonResidentHeader.Fragments; } 20 | } 21 | 22 | public override AttributeResidentAllow AllowedResidentStates 23 | { 24 | get 25 | { 26 | return AttributeResidentAllow.Resident | AttributeResidentAllow.NonResident; 27 | } 28 | } 29 | 30 | internal override void ParseAttributeResidentBody(byte[] data, int maxLength, int offset) 31 | { 32 | base.ParseAttributeResidentBody(data, maxLength, offset); 33 | 34 | DataBytes = new byte[ResidentHeader.ContentLength]; 35 | Array.Copy(data, offset, DataBytes, 0, DataBytes.Length); 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /LineOS/NTFS/Model/Attributes/AttributeExtendedAttributeInformation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using LineOS.NTFS.Model.Enums; 3 | 4 | namespace LineOS.NTFS.Model.Attributes 5 | { 6 | public class AttributeExtendedAttributeInformation : Attribute 7 | { 8 | public ushort SizePackedEA { get; set; } 9 | public ushort CountNeedEA { get; set; } 10 | public uint SizeUnpackedEA { get; set; } 11 | 12 | public override AttributeResidentAllow AllowedResidentStates 13 | { 14 | get 15 | { 16 | return AttributeResidentAllow.Resident; 17 | } 18 | } 19 | 20 | internal override void ParseAttributeResidentBody(byte[] data, int maxLength, int offset) 21 | { 22 | base.ParseAttributeResidentBody(data, maxLength, offset); 23 | 24 | // Debug.Assert(maxLength >= 8); 25 | 26 | SizePackedEA = BitConverter.ToUInt16(data, offset); 27 | CountNeedEA = BitConverter.ToUInt16(data, offset + 2); 28 | SizeUnpackedEA = BitConverter.ToUInt32(data, offset + 4); 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /LineOS/NTFS/Model/Attributes/AttributeExtendedAttributes.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using LineOS.NTFS.Model.Enums; 3 | using LineOS.NTFS.Utility; 4 | 5 | namespace LineOS.NTFS.Model.Attributes 6 | { 7 | public class AttributeExtendedAttributes : Attribute 8 | { 9 | public List ExtendedAttributes { get; set; } 10 | 11 | public override AttributeResidentAllow AllowedResidentStates 12 | { 13 | get 14 | { 15 | return AttributeResidentAllow.Resident | AttributeResidentAllow.NonResident; 16 | } 17 | } 18 | 19 | internal override void ParseAttributeNonResidentBody(Ntfs ntfsInfo) 20 | { 21 | base.ParseAttributeNonResidentBody(ntfsInfo); 22 | 23 | // Get all chunks 24 | byte[] data = NtfsUtils.ReadFragments(ntfsInfo, NonResidentHeader.Fragments); 25 | 26 | // Parse 27 | // Debug.Assert(data.Length >= 8); 28 | 29 | List extendedAttributes = new List(); 30 | int pointer = 0; 31 | while (pointer + 8 <= data.Length) // 8 is the minimum size of an ExtendedAttribute 32 | { 33 | if (ExtendedAttribute.GetSize(data, pointer) <= 0) 34 | break; 35 | 36 | ExtendedAttribute ea = ExtendedAttribute.ParseData(data, (int)NonResidentHeader.ContentSize, pointer); 37 | 38 | extendedAttributes.Add(ea); 39 | 40 | pointer += ea.Size; 41 | } 42 | 43 | ExtendedAttributes = extendedAttributes; 44 | } 45 | 46 | internal override void ParseAttributeResidentBody(byte[] data, int maxLength, int offset) 47 | { 48 | base.ParseAttributeResidentBody(data, maxLength, offset); 49 | 50 | // Debug.Assert(maxLength >= 8); 51 | 52 | List extendedAttributes = new List(); 53 | int pointer = offset; 54 | while (pointer + 8 <= offset + maxLength) // 8 is the minimum size of an ExtendedAttribute 55 | { 56 | if (ExtendedAttribute.GetSize(data, pointer) < 0) 57 | break; 58 | 59 | ExtendedAttribute ea = ExtendedAttribute.ParseData(data, (int)ResidentHeader.ContentLength, pointer); 60 | 61 | extendedAttributes.Add(ea); 62 | 63 | pointer += ea.Size; 64 | } 65 | 66 | ExtendedAttributes = extendedAttributes; 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /LineOS/NTFS/Model/Attributes/AttributeFileName.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Text; 4 | using LineOS.NTFS.Model.Enums; 5 | using LineOS.NTFS.Utility; 6 | 7 | namespace LineOS.NTFS.Model.Attributes 8 | { 9 | public class AttributeFileName : Attribute 10 | { 11 | public FileReference ParentDirectory { get; set; } 12 | public DateTime CTime { get; set; } 13 | public DateTime ATime { get; set; } 14 | public DateTime MTime { get; set; } 15 | public DateTime RTime { get; set; } 16 | public ulong AllocatedSize { get; set; } 17 | public ulong RealSize { get; set; } 18 | public FileAttributes FileFlags { get; set; } 19 | public uint ReservedEAsReparse { get; set; } 20 | public byte FilenameLength { get; set; } 21 | public FileNamespace FilenameNamespace { get; set; } 22 | public string FileName { get; set; } 23 | 24 | public override AttributeResidentAllow AllowedResidentStates 25 | { 26 | get 27 | { 28 | return AttributeResidentAllow.Resident; 29 | } 30 | } 31 | 32 | internal override void ParseAttributeResidentBody(byte[] data, int maxLength, int offset) 33 | { 34 | base.ParseAttributeResidentBody(data, maxLength, offset); 35 | 36 | ParentDirectory = new FileReference(BitConverter.ToUInt64(data, offset)); 37 | CTime = NtfsUtils.FromWinFileTime(data, offset + 8); 38 | ATime = NtfsUtils.FromWinFileTime(data, offset + 16); 39 | MTime = NtfsUtils.FromWinFileTime(data, offset + 24); 40 | RTime = NtfsUtils.FromWinFileTime(data, offset + 32); 41 | AllocatedSize = BitConverter.ToUInt64(data, offset + 40); 42 | RealSize = BitConverter.ToUInt64(data, offset + 48); 43 | FileFlags = (FileAttributes)BitConverter.ToInt32(data, offset + 56); 44 | ReservedEAsReparse = BitConverter.ToUInt32(data, offset + 60); 45 | FilenameLength = data[offset + 64]; 46 | FilenameNamespace = (FileNamespace) data[offset + 65]; 47 | 48 | // Debug.Assert(maxLength >= 66 + FilenameLength * 2); 49 | 50 | FileName = Encoding.Unicode.GetString(data, offset + 66, FilenameLength * 2); 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /LineOS/NTFS/Model/Attributes/AttributeGeneric.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using LineOS.NTFS.Model.Enums; 3 | 4 | namespace LineOS.NTFS.Model.Attributes 5 | { 6 | public class AttributeGeneric : Attribute 7 | { 8 | public byte[] Data { get; set; } 9 | 10 | public override AttributeResidentAllow AllowedResidentStates 11 | { 12 | get 13 | { 14 | return AttributeResidentAllow.Resident; 15 | } 16 | } 17 | 18 | internal override void ParseAttributeResidentBody(byte[] data, int maxLength, int offset) 19 | { 20 | base.ParseAttributeResidentBody(data, maxLength, offset); 21 | 22 | // Get data 23 | Data = new byte[maxLength]; 24 | Array.Copy(data, offset, Data, 0, maxLength); 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /LineOS/NTFS/Model/Attributes/AttributeIndexAllocation.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using LineOS.NTFS.Model.Enums; 3 | using LineOS.NTFS.Utility; 4 | 5 | namespace LineOS.NTFS.Model.Attributes 6 | { 7 | public class AttributeIndexAllocation : Attribute 8 | { 9 | public List Indexes { get; set; } 10 | public List Entries { get; set; } 11 | 12 | public override AttributeResidentAllow AllowedResidentStates 13 | { 14 | get 15 | { 16 | return AttributeResidentAllow.NonResident; 17 | } 18 | } 19 | 20 | internal override void ParseAttributeNonResidentBody(Ntfs ntfsInfo) 21 | { 22 | byte[] data = NtfsUtils.ReadFragments(ntfsInfo, NonResidentHeader.Fragments); 23 | 24 | List indexes = new List(); 25 | List entries = new List(); 26 | 27 | // Parse 28 | for (int i = 0; i < NonResidentHeader.Fragments.Count; i++) 29 | { 30 | for (int j = 0; j < NonResidentHeader.Fragments[i].Clusters; j++) 31 | { 32 | int offset = (int)((NonResidentHeader.Fragments[i].StartingVCN - NonResidentHeader.Fragments[0].StartingVCN) * ntfsInfo.BytesPerCluster + j * ntfsInfo.BytesPerCluster); 33 | 34 | if (!IndexAllocationChunk.IsIndexAllocationChunk(data, offset)) 35 | continue; 36 | 37 | IndexAllocationChunk index = IndexAllocationChunk.ParseBody(ntfsInfo, data, offset); 38 | 39 | indexes.Add(index); 40 | foreach(var f in index.Entries) 41 | entries.Add(f); 42 | } 43 | } 44 | 45 | Indexes = indexes; 46 | Entries = entries; 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /LineOS/NTFS/Model/Attributes/AttributeIndexRoot.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using LineOS.NTFS.Model.Enums; 4 | 5 | namespace LineOS.NTFS.Model.Attributes 6 | { 7 | public class AttributeIndexRoot : Attribute 8 | { 9 | // Root 10 | public AttributeType IndexType { get; set; } 11 | public uint CollationRule { get; set; } 12 | public uint IndexAllocationSize { get; set; } 13 | public byte ClustersPrIndexRecord { get; set; } 14 | 15 | // Header 16 | public uint OffsetToFirstIndex { get; set; } 17 | public uint SizeOfIndexTotal { get; set; } 18 | public uint SizeOfIndexAllocated { get; set; } 19 | public MFTIndexRootFlags IndexFlags { get; set; } 20 | 21 | public List Entries { get; set; } 22 | 23 | public override AttributeResidentAllow AllowedResidentStates 24 | { 25 | get 26 | { 27 | return AttributeResidentAllow.Resident; 28 | } 29 | } 30 | 31 | internal override void ParseAttributeResidentBody(byte[] data, int maxLength, int offset) 32 | { 33 | base.ParseAttributeResidentBody(data, maxLength, offset); 34 | 35 | // Debug.Assert(maxLength >= 32); 36 | 37 | IndexType = (AttributeType)BitConverter.ToUInt32(data, offset); 38 | CollationRule = BitConverter.ToUInt32(data, offset + 4); 39 | IndexAllocationSize = BitConverter.ToUInt32(data, offset + 8); 40 | ClustersPrIndexRecord = data[offset + 12]; 41 | 42 | OffsetToFirstIndex = BitConverter.ToUInt32(data, offset + 16); 43 | SizeOfIndexTotal = BitConverter.ToUInt32(data, offset + 20); 44 | SizeOfIndexAllocated = BitConverter.ToUInt32(data, offset + 24); 45 | IndexFlags = (MFTIndexRootFlags)data[offset + 28]; 46 | 47 | List entries = new List(); 48 | 49 | // Parse entries 50 | int pointer = offset + 32; 51 | while (pointer <= offset + SizeOfIndexTotal + 32) 52 | { 53 | IndexEntry entry = IndexEntry.ParseData(data, (int)SizeOfIndexTotal - (pointer - offset) + 32, pointer); 54 | 55 | if ((entry.Flags & MFTIndexEntryFlags.LastEntry) != 0) 56 | break; 57 | 58 | entries.Add(entry); 59 | 60 | pointer += entry.Size; 61 | } 62 | 63 | Entries = entries; 64 | } 65 | } 66 | } -------------------------------------------------------------------------------- /LineOS/NTFS/Model/Attributes/AttributeList.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using LineOS.NTFS.Model.Enums; 4 | using LineOS.NTFS.Utility; 5 | 6 | namespace LineOS.NTFS.Model.Attributes 7 | { 8 | public class AttributeList : Attribute 9 | { 10 | public List Items { get; set; } 11 | 12 | public override AttributeResidentAllow AllowedResidentStates 13 | { 14 | get 15 | { 16 | return AttributeResidentAllow.Resident | AttributeResidentAllow.NonResident; 17 | } 18 | } 19 | 20 | internal override void ParseAttributeResidentBody(byte[] data, int maxLength, int offset) 21 | { 22 | base.ParseAttributeResidentBody(data, maxLength, offset); 23 | 24 | // Debug.Assert(maxLength >= ResidentHeader.ContentLength); 25 | 26 | List results = new List(); 27 | 28 | int pointer = offset; 29 | while (pointer + 26 <= offset + maxLength) // 26 is the smallest possible MFTAttributeListItem 30 | { 31 | AttributeListItem item = AttributeListItem.ParseListItem(data, Math.Min(data.Length - pointer, maxLength), pointer); 32 | 33 | if (item.Type == AttributeType.EndOfAttributes) 34 | break; 35 | 36 | results.Add(item); 37 | 38 | pointer += item.Length; 39 | } 40 | 41 | Items = results; 42 | } 43 | 44 | internal override void ParseAttributeNonResidentBody(Ntfs ntfsInfo) 45 | { 46 | base.ParseAttributeNonResidentBody(ntfsInfo); 47 | 48 | // Get all chunks 49 | byte[] data = NtfsUtils.ReadFragments(ntfsInfo, NonResidentHeader.Fragments); 50 | 51 | // Parse 52 | List results = new List(); 53 | 54 | int pointer = 0; 55 | int contentSize = (int) NonResidentHeader.ContentSize; 56 | while (pointer + 26 <= contentSize) // 26 is the smallest possible MFTAttributeListItem 57 | { 58 | AttributeListItem item = AttributeListItem.ParseListItem(data, data.Length - pointer, pointer); 59 | 60 | if (item.Type == AttributeType.EndOfAttributes) 61 | break; 62 | 63 | if (item.Length == 0) 64 | break; 65 | 66 | results.Add(item); 67 | 68 | pointer += item.Length; 69 | } 70 | 71 | // Debug.Assert(pointer == contentSize); 72 | 73 | Items = results; 74 | } 75 | } 76 | } -------------------------------------------------------------------------------- /LineOS/NTFS/Model/Attributes/AttributeListItem.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using LineOS.NTFS.Model.Enums; 4 | 5 | namespace LineOS.NTFS.Model.Attributes 6 | { 7 | public class AttributeListItem 8 | { 9 | public AttributeType Type { get; set; } 10 | public ushort Length { get; set; } 11 | public byte NameLength { get; set; } 12 | public byte NameOffset { get; set; } 13 | public ulong StartingVCN { get; set; } 14 | public FileReference BaseFile { get; set; } 15 | public ushort AttributeId { get; set; } 16 | public string Name { get; set; } 17 | 18 | public static AttributeListItem ParseListItem(byte[] data, int maxLength, int offset) 19 | { 20 | // Debug.Assert(maxLength >= 26); 21 | 22 | AttributeListItem res = new AttributeListItem(); 23 | 24 | res.Type = (AttributeType)BitConverter.ToUInt32(data, offset); 25 | res.Length = BitConverter.ToUInt16(data, offset + 4); 26 | res.NameLength = data[offset + 6]; 27 | res.NameOffset = data[offset + 7]; 28 | res.StartingVCN = BitConverter.ToUInt64(data, offset + 8); 29 | res.BaseFile = new FileReference(BitConverter.ToUInt64(data, offset + 16)); 30 | res.AttributeId = BitConverter.ToUInt16(data, offset + 24); 31 | 32 | // Debug.Assert(maxLength >= res.NameOffset + res.NameLength * 2); 33 | res.Name = Encoding.Unicode.GetString(data, offset + res.NameOffset, res.NameLength * 2); 34 | 35 | return res; 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /LineOS/NTFS/Model/Attributes/AttributeLoggedUtilityStream.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using LineOS.NTFS.Model.Enums; 3 | 4 | namespace LineOS.NTFS.Model.Attributes 5 | { 6 | public class AttributeLoggedUtilityStream : Attribute 7 | { 8 | public byte[] Data { get; set; } 9 | 10 | public override AttributeResidentAllow AllowedResidentStates 11 | { 12 | get 13 | { 14 | return AttributeResidentAllow.Resident; 15 | } 16 | } 17 | 18 | internal override void ParseAttributeResidentBody(byte[] data, int maxLength, int offset) 19 | { 20 | base.ParseAttributeResidentBody(data, maxLength, offset); 21 | 22 | // Debug.Assert(maxLength >= 1); 23 | 24 | Data = new byte[maxLength]; 25 | Array.Copy(data, offset, Data, 0, maxLength); 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /LineOS/NTFS/Model/Attributes/AttributeObjectId.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using LineOS.NTFS.Model.Enums; 3 | 4 | namespace LineOS.NTFS.Model.Attributes 5 | { 6 | public class AttributeObjectId : Attribute 7 | { 8 | public Guid ObjectId { get; set; } 9 | public Guid BithVolumeId { get; set; } 10 | public Guid BithObjectId { get; set; } 11 | public Guid DomainId { get; set; } 12 | 13 | public override AttributeResidentAllow AllowedResidentStates 14 | { 15 | get 16 | { 17 | return AttributeResidentAllow.Resident; 18 | } 19 | } 20 | 21 | internal override void ParseAttributeResidentBody(byte[] data, int maxLength, int offset) 22 | { 23 | base.ParseAttributeResidentBody(data, maxLength, offset); 24 | 25 | // Debug.Assert(maxLength >= 16); 26 | 27 | byte[] guidBytes = new byte[16]; 28 | 29 | Array.Copy(data, offset, guidBytes, 0, 16); 30 | ObjectId = new Guid(guidBytes); 31 | 32 | // Parse as much as possible 33 | if (maxLength < 32) 34 | return; 35 | 36 | Array.Copy(data, offset + 16, guidBytes, 0, 16); 37 | BithVolumeId = new Guid(guidBytes); 38 | 39 | if (maxLength < 48) 40 | return; 41 | 42 | Array.Copy(data, offset + 32, guidBytes, 0, 16); 43 | BithObjectId = new Guid(guidBytes); 44 | 45 | if (maxLength < 64) 46 | return; 47 | 48 | Array.Copy(data, offset + 48, guidBytes, 0, 16); 49 | DomainId = new Guid(guidBytes); 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /LineOS/NTFS/Model/Attributes/AttributeSecurityDescriptor.cs: -------------------------------------------------------------------------------- 1 | using LineOS.NTFS.Model.Enums; 2 | 3 | namespace LineOS.NTFS.Model.Attributes 4 | { 5 | public class AttributeSecurityDescriptor : Attribute 6 | { 7 | /*public byte Revision { get; set; } 8 | public ControlFlags ControlFlags { get; set; } 9 | public uint OffsetToUserSID { get; set; } 10 | public uint OffsetToGroupSID { get; set; } 11 | public uint OffsetToSACL { get; set; } 12 | public uint OffsetToDACL { get; set; } 13 | 14 | public ACL SACL { get; set; } 15 | public ACL DACL { get; set; } 16 | public SecurityIdentifier UserSID { get; set; } 17 | public SecurityIdentifier GroupSID { get; set; }*/ 18 | 19 | public override AttributeResidentAllow AllowedResidentStates 20 | { 21 | get 22 | { 23 | return AttributeResidentAllow.Resident; 24 | } 25 | } 26 | 27 | internal override void ParseAttributeResidentBody(byte[] data, int maxLength, int offset) 28 | { 29 | base.ParseAttributeResidentBody(data, maxLength, offset); 30 | /* 31 | // Debug.Assert(maxLength >= 20); 32 | 33 | Revision = data[offset]; 34 | ControlFlags = (ControlFlags)BitConverter.ToUInt16(data, offset + 2); 35 | OffsetToUserSID = BitConverter.ToUInt32(data, offset + 4); 36 | OffsetToGroupSID = BitConverter.ToUInt32(data, offset + 8); 37 | OffsetToSACL = BitConverter.ToUInt32(data, offset + 12); 38 | OffsetToDACL = BitConverter.ToUInt32(data, offset + 16); 39 | 40 | if (OffsetToUserSID != 0) 41 | UserSID = new SecurityIdentifier(data, offset + (int)OffsetToUserSID); 42 | if (OffsetToGroupSID != 0) 43 | GroupSID = new SecurityIdentifier(data, offset + (int)OffsetToGroupSID); 44 | 45 | if (OffsetToSACL != 0 && ControlFlags.HasFlag(ControlFlags.SystemAclPresent)) 46 | { 47 | SACL = ACL.ParseACL(data, 8, (int)(offset + OffsetToSACL)); 48 | } 49 | if (OffsetToDACL != 0 && ControlFlags.HasFlag(ControlFlags.DiscretionaryAclPresent)) 50 | { 51 | DACL = ACL.ParseACL(data, 8, (int)(offset + OffsetToDACL)); 52 | }*/ 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /LineOS/NTFS/Model/Attributes/AttributeStandardInformation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using LineOS.NTFS.Model.Enums; 4 | using LineOS.NTFS.Utility; 5 | 6 | namespace LineOS.NTFS.Model.Attributes 7 | { 8 | public class AttributeStandardInformation : Attribute 9 | { 10 | public DateTime TimeCreated { get; set; } 11 | public DateTime TimeModified { get; set; } 12 | public DateTime TimeMftModified { get; set; } 13 | public DateTime TimeAccessed { get; set; } 14 | public FileAttributes DosPermissions { get; set; } 15 | public uint MaxmiumVersions { get; set; } 16 | public uint VersionNumber { get; set; } 17 | public uint ClassId { get; set; } 18 | public uint OwnerId { get; set; } 19 | public uint SecurityId { get; set; } 20 | public ulong QuotaCharged { get; set; } 21 | public ulong USN { get; set; } 22 | 23 | public override AttributeResidentAllow AllowedResidentStates 24 | { 25 | get 26 | { 27 | return AttributeResidentAllow.Resident; 28 | } 29 | } 30 | 31 | internal override void ParseAttributeResidentBody(byte[] data, int maxLength, int offset) 32 | { 33 | base.ParseAttributeResidentBody(data, maxLength, offset); 34 | 35 | // Debug.Assert(maxLength >= 48); 36 | 37 | TimeCreated = NtfsUtils.FromWinFileTime(data, offset); 38 | TimeModified = NtfsUtils.FromWinFileTime(data, offset + 8); 39 | TimeMftModified = NtfsUtils.FromWinFileTime(data, offset + 16); 40 | TimeAccessed = NtfsUtils.FromWinFileTime(data, offset + 24); 41 | DosPermissions = (FileAttributes)BitConverter.ToInt32(data, offset + 32); 42 | 43 | MaxmiumVersions = BitConverter.ToUInt32(data, offset + 36); 44 | VersionNumber = BitConverter.ToUInt32(data, offset + 40); 45 | ClassId = BitConverter.ToUInt32(data, offset + 44); 46 | 47 | // The below fields are for version 3.0+ 48 | if (NonResidentFlag == ResidentFlag.Resident && ResidentHeader.ContentLength >= 72) 49 | { 50 | // Debug.Assert(maxLength >= 72); 51 | 52 | OwnerId = BitConverter.ToUInt32(data, offset + 48); 53 | SecurityId = BitConverter.ToUInt32(data, offset + 52); 54 | QuotaCharged = BitConverter.ToUInt64(data, offset + 56); 55 | USN = BitConverter.ToUInt64(data, offset + 64); 56 | } 57 | } 58 | 59 | public override int GetSaveLength() 60 | { 61 | // TODO: Get the actual NTFS Version in here to check against 62 | if (OwnerId != 0 || SecurityId != 0 || QuotaCharged != 0 || USN != 0) 63 | { 64 | return base.GetSaveLength() + 72; 65 | } 66 | 67 | return base.GetSaveLength() + 48; 68 | } 69 | 70 | public override void Save(byte[] buffer, int offset) 71 | { 72 | base.Save(buffer, offset); 73 | 74 | LittleEndianConverter.GetBytes(buffer, offset, TimeCreated); 75 | LittleEndianConverter.GetBytes(buffer, offset + 8, TimeModified); 76 | LittleEndianConverter.GetBytes(buffer, offset + 16, TimeMftModified); 77 | LittleEndianConverter.GetBytes(buffer, offset + 24, TimeAccessed); 78 | LittleEndianConverter.GetBytes(buffer, offset + 32, (int)DosPermissions); 79 | 80 | LittleEndianConverter.GetBytes(buffer, offset + 36, MaxmiumVersions); 81 | LittleEndianConverter.GetBytes(buffer, offset + 40, VersionNumber); 82 | LittleEndianConverter.GetBytes(buffer, offset + 44, ClassId); 83 | 84 | // TODO: Get the actual NTFS Version in here to check against 85 | if (OwnerId != 0 || SecurityId != 0 || QuotaCharged != 0 || USN != 0) 86 | { 87 | LittleEndianConverter.GetBytes(buffer, offset + 48, OwnerId); 88 | LittleEndianConverter.GetBytes(buffer, offset + 52, SecurityId); 89 | LittleEndianConverter.GetBytes(buffer, offset + 56, QuotaCharged); 90 | LittleEndianConverter.GetBytes(buffer, offset + 64, USN); 91 | } 92 | } 93 | } 94 | } -------------------------------------------------------------------------------- /LineOS/NTFS/Model/Attributes/AttributeVolumeInformation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using LineOS.NTFS.Model.Enums; 3 | 4 | namespace LineOS.NTFS.Model.Attributes 5 | { 6 | public class AttributeVolumeInformation : Attribute 7 | { 8 | public ulong Reserved { get; set; } 9 | public byte MajorVersion { get; set; } 10 | public byte MinorVersion { get; set; } 11 | public VolumeInformationFlags VolumeInformationFlag { get; set; } 12 | 13 | public override AttributeResidentAllow AllowedResidentStates 14 | { 15 | get 16 | { 17 | return AttributeResidentAllow.Resident; 18 | } 19 | } 20 | 21 | internal override void ParseAttributeResidentBody(byte[] data, int maxLength, int offset) 22 | { 23 | base.ParseAttributeResidentBody(data, maxLength, offset); 24 | 25 | // Debug.Assert(maxLength >= 16); 26 | 27 | Reserved = BitConverter.ToUInt64(data, offset); 28 | MajorVersion = data[offset + 8]; 29 | MinorVersion = data[offset + 9]; 30 | VolumeInformationFlag = (VolumeInformationFlags)BitConverter.ToUInt16(data, offset + 10); 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /LineOS/NTFS/Model/Attributes/AttributeVolumeName.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using LineOS.NTFS.Model.Enums; 3 | 4 | namespace LineOS.NTFS.Model.Attributes 5 | { 6 | public class AttributeVolumeName : Attribute 7 | { 8 | public string VolumeName { get; set; } 9 | 10 | public override AttributeResidentAllow AllowedResidentStates 11 | { 12 | get 13 | { 14 | return AttributeResidentAllow.Resident; 15 | } 16 | } 17 | 18 | internal override void ParseAttributeResidentBody(byte[] data, int maxLength, int offset) 19 | { 20 | base.ParseAttributeResidentBody(data, maxLength, offset); 21 | 22 | // Debug.Assert(maxLength >= ResidentHeader.ContentLength); 23 | 24 | VolumeName = Encoding.Unicode.GetString(data, offset, (int)ResidentHeader.ContentLength); 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /LineOS/NTFS/Model/Attributes/ExtendedAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | using System.Text; 4 | using LineOS.NTFS.Model.Enums; 5 | 6 | namespace LineOS.NTFS.Model.Attributes 7 | { 8 | public class ExtendedAttribute 9 | { 10 | public int Size { get; set; } 11 | public MFTEAFlags EAFlag { get; set; } 12 | public byte NameLength { get; set; } 13 | public ushort ValueLength { get; set; } 14 | public string Name { get; set; } 15 | public byte[] Value { get; set; } 16 | 17 | public static int GetSize(byte[] data, int offset) 18 | { 19 | return BitConverter.ToInt32(data, offset); 20 | } 21 | 22 | public static ExtendedAttribute ParseData(byte[] data, int maxLength, int offset) 23 | { 24 | 25 | ExtendedAttribute res = new ExtendedAttribute(); 26 | 27 | res.Size = BitConverter.ToInt32(data, offset); 28 | res.EAFlag = (MFTEAFlags)data[offset + 4]; 29 | res.NameLength = data[offset + 5]; 30 | res.ValueLength = BitConverter.ToUInt16(data, offset + 6); 31 | 32 | // Debug.Assert(res.Size <= maxLength); 33 | // Debug.Assert(res.NameLength <= res.Size); 34 | // Debug.Assert(res.ValueLength <= res.Size); 35 | 36 | res.Name = Encoding.ASCII.GetString(data, offset + 8, res.NameLength); 37 | res.Value = new byte[res.ValueLength]; 38 | Array.Copy(data, offset + 8 + res.NameLength, res.Value, 0, res.ValueLength); 39 | 40 | return res; 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /LineOS/NTFS/Model/DataFragment.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace LineOS.NTFS.Model 5 | { 6 | public class DataFragment 7 | { 8 | /// 9 | /// Note. This is not the size in bytes of this fragment. It is a very compact NTFS-specific way of writing lengths. 10 | /// Be warned when using it. 11 | /// 12 | public byte FragmentSizeBytes { get; set; } 13 | public long Clusters { get; set; } 14 | public byte CompressedClusters { get; set; } 15 | public long LCN { get; set; } 16 | public long StartingVCN { get; set; } 17 | public int ThisObjectLength { get; private set; } 18 | 19 | /// 20 | /// If this fragment is sparse, there is not data on the disk to reflect the data in the file. 21 | /// This chunk is therefore all zeroes. 22 | /// 23 | public bool IsSparseFragment 24 | { 25 | get { return LCN == 0; } 26 | } 27 | 28 | /// 29 | /// If this fragment is compressed, it will contain some clusters on disk (The Clusters property) which contain actual (compressed) data. 30 | /// After that, there is a number of clusters (CompressedClusters property) which act as 'fillers' for the decompressed data. It does not exist on disk. 31 | /// 32 | public bool IsCompressed 33 | { 34 | get { return CompressedClusters != 0; } 35 | } 36 | 37 | private int GetSaveLength(long previousLcn) 38 | { 39 | long deltaLcn = LCN == 0 ? 0 : LCN - previousLcn; 40 | 41 | byte offsetBytes = CalculateBytesNecessary(deltaLcn); 42 | byte lengthBytes = CalculateBytesNecessary(Clusters); 43 | 44 | int nextBytes = 0; 45 | if (IsCompressed) 46 | { 47 | nextBytes = 1 + CalculateBytesNecessary(CompressedClusters); 48 | } 49 | 50 | return 1 + offsetBytes + lengthBytes + nextBytes; 51 | } 52 | 53 | private void Save(byte[] buffer, int offset, long previousLcn) 54 | { 55 | long deltaLcn = LCN == 0 ? 0 : LCN - previousLcn; 56 | long length = Clusters; 57 | 58 | byte offsetBytes = CalculateBytesNecessary(deltaLcn); 59 | byte lengthBytes = CalculateBytesNecessary(length); 60 | 61 | buffer[offset] = (byte)((offsetBytes << 4) | lengthBytes); 62 | 63 | for (int i = 0; i < lengthBytes; i++) 64 | { 65 | buffer[offset + 1 + i] = (byte)(length & 0xFF); 66 | length >>= 8; 67 | } 68 | 69 | for (int i = 0; i < offsetBytes; i++) 70 | { 71 | buffer[offset + 1 + lengthBytes + i] = (byte)(deltaLcn & 0xFF); 72 | deltaLcn >>= 8; 73 | } 74 | 75 | // Make followup compressed cluster 76 | if (IsCompressed) 77 | { 78 | length = CompressedClusters; 79 | lengthBytes = CalculateBytesNecessary(length); 80 | 81 | buffer[offset + 1 + lengthBytes + offsetBytes] = lengthBytes; 82 | 83 | for (int i = 0; i < lengthBytes; i++) 84 | { 85 | buffer[offset + 1 + lengthBytes + offsetBytes + 1 + i] = (byte)(length & 0xFF); 86 | length >>= 8; 87 | } 88 | } 89 | } 90 | 91 | private static byte CalculateBytesNecessary(long value) 92 | { 93 | if (value == 0) 94 | return 0; 95 | 96 | bool isNegative = false; 97 | if (value < 0) 98 | { 99 | value = -value; 100 | isNegative = true; 101 | } 102 | 103 | long tester = 0x80L; 104 | for (byte i = 0; i < 8; i++) 105 | { 106 | if (tester > value) 107 | return (byte)(i + 1); 108 | 109 | tester <<= 8; 110 | } 111 | 112 | throw new Exception(); 113 | } 114 | 115 | public static int GetSaveLength(List fragments) 116 | { 117 | long lcn = 0; 118 | int saveLength = 0; 119 | 120 | foreach (DataFragment fragment in fragments) 121 | { 122 | saveLength += fragment.GetSaveLength(lcn); 123 | 124 | if (fragment.LCN != 0) 125 | lcn = fragment.LCN; 126 | } 127 | 128 | return saveLength; 129 | } 130 | 131 | public static void Save(byte[] buffer, int offset, List fragments) 132 | { 133 | long lcn = 0; 134 | int pointer = offset; 135 | 136 | foreach (DataFragment fragment in fragments) 137 | { 138 | int saveLength = fragment.GetSaveLength(lcn); 139 | fragment.Save(buffer, pointer, lcn); 140 | 141 | pointer += saveLength; 142 | if (fragment.LCN != 0) 143 | lcn = fragment.LCN; 144 | } 145 | } 146 | 147 | public static DataFragment ParseFragment(byte[] data, long previousLcn, int offset) 148 | { 149 | DataFragment res = new DataFragment(); 150 | 151 | res.FragmentSizeBytes = data[offset]; 152 | byte offsetBytes = (byte)(res.FragmentSizeBytes >> 4); 153 | byte countBytes = (byte)(res.FragmentSizeBytes & 0x0F); 154 | 155 | res.ThisObjectLength = 1 + countBytes + offsetBytes; 156 | 157 | if (countBytes == 0) 158 | { 159 | res.FragmentSizeBytes = 0; 160 | return res; 161 | } 162 | 163 | offsetBytes = (byte)(offsetBytes & 0xF7); // 0xF7: 1111 0111 164 | //// Debug.Assert(countBytes <= 8, "Fragment metadata exceeded 8 bytes"); 165 | //// Debug.Assert(offsetBytes <= 8, "Fragment metadata exceeded 8 bytes"); 166 | 167 | byte[] tmpData = new byte[8]; 168 | Array.Copy(data, offset + 1, tmpData, 0, countBytes); 169 | 170 | res.Clusters = BitConverter.ToInt64(tmpData, 0); 171 | res.LCN = previousLcn; 172 | 173 | if (offsetBytes == 0) 174 | { 175 | // Sparse chunk 176 | res.LCN = 0; 177 | } 178 | else 179 | { 180 | long deltaLcn = 0; 181 | 182 | for (int i = offsetBytes - 1; i >= 0; i--) 183 | { 184 | deltaLcn = deltaLcn << 8; 185 | deltaLcn += data[offset + 1 + countBytes + i]; 186 | } 187 | 188 | // Is negative? 189 | long negativeValue = (long)128 << 8 * (offsetBytes - 1); 190 | if ((deltaLcn & negativeValue) == negativeValue) 191 | { 192 | // Negtive 193 | // Set the remaining bytes to 0xFF 194 | long tmp = 0xFF; 195 | for (int i = 0; i < 8 - offsetBytes; i++) 196 | { 197 | tmp = tmp << 8; 198 | tmp |= 0xFF; 199 | } 200 | 201 | for (int i = 8 - offsetBytes; i < 8; i++) 202 | { 203 | tmp = tmp << 8; 204 | } 205 | 206 | deltaLcn |= tmp; 207 | } 208 | 209 | res.LCN = res.LCN + deltaLcn; 210 | } 211 | 212 | return res; 213 | } 214 | 215 | public static List ParseFragments(byte[] data, int maxLength, int offset, long startingVCN, long endingVCN) 216 | { 217 | //// Debug.Assert(data.Length - offset >= maxLength); 218 | //// Debug.Assert(0 <= offset && offset <= data.Length); 219 | 220 | List fragments = new List(); 221 | 222 | long vcn = startingVCN; 223 | 224 | int pointer = offset; 225 | long lastLcn = 0; 226 | while (pointer < offset + maxLength) 227 | { 228 | // // Debug.Assert(pointer <= offset + maxLength); 229 | 230 | DataFragment fragment = ParseFragment(data, lastLcn, pointer); 231 | 232 | pointer += fragment.ThisObjectLength; 233 | 234 | if (fragment.FragmentSizeBytes == 0) 235 | // Last fragment 236 | break; 237 | 238 | fragment.StartingVCN = vcn; 239 | 240 | vcn += fragment.Clusters; 241 | 242 | if (!fragment.IsSparseFragment) 243 | // Don't count sparse fragments for offsets 244 | lastLcn = fragment.LCN; 245 | 246 | fragments.Add(fragment); 247 | } 248 | 249 | // Checks 250 | //// Debug.Assert(fragments.Count == 0 || startingVCN == fragments[0].StartingVCN); 251 | //// Debug.Assert(endingVCN == vcn - 1); 252 | 253 | // Return 254 | return fragments; 255 | } 256 | 257 | public static void CompactCompressedFragments(List fragments) 258 | { 259 | for (int i = 0; i < fragments.Count - 1; i++) 260 | { 261 | if (fragments[i + 1].IsSparseFragment && 262 | (fragments[i].Clusters + fragments[i + 1].Clusters) % 16 == 0 && 263 | fragments[i + 1].Clusters < 16) 264 | { 265 | // Compact 266 | fragments[i].CompressedClusters = (byte)fragments[i + 1].Clusters; 267 | fragments.RemoveAt(i + 1); 268 | 269 | i--; 270 | } 271 | } 272 | } 273 | 274 | public static void CompactFragmentList(List fragments) 275 | { 276 | for (int j = 0; j < fragments.Count - 1; j++) 277 | { 278 | if (!fragments[j].IsCompressed && !fragments[j].IsSparseFragment && 279 | !fragments[j + 1].IsCompressed && !fragments[j + 1].IsSparseFragment && 280 | fragments[j].LCN + fragments[j].Clusters == fragments[j + 1].LCN) 281 | { 282 | // Compact 283 | fragments[j].Clusters += fragments[j + 1].Clusters; 284 | 285 | fragments.RemoveAt(j + 1); 286 | 287 | j--; 288 | } 289 | } 290 | } 291 | } 292 | } 293 | -------------------------------------------------------------------------------- /LineOS/NTFS/Model/Enums/AttributeFlags.cs: -------------------------------------------------------------------------------- 1 | namespace LineOS.NTFS.Model.Enums 2 | { 3 | public enum AttributeFlags : ushort 4 | { 5 | Compressed = 0x0001, 6 | Encrypted = 0x4000, 7 | Sparse = 0x8000 8 | } 9 | } -------------------------------------------------------------------------------- /LineOS/NTFS/Model/Enums/AttributeResidentAllow.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace LineOS.NTFS.Model.Enums 4 | { 5 | [Flags] 6 | public enum AttributeResidentAllow 7 | { 8 | Resident = 1, 9 | NonResident = 2 10 | } 11 | } -------------------------------------------------------------------------------- /LineOS/NTFS/Model/Enums/AttributeType.cs: -------------------------------------------------------------------------------- 1 | namespace LineOS.NTFS.Model.Enums 2 | { 3 | // ReSharper disable InconsistentNaming 4 | public enum AttributeType : uint 5 | { 6 | Unknown = 0, 7 | STANDARD_INFORMATION = 0x10, 8 | ATTRIBUTE_LIST = 0x20, 9 | FILE_NAME = 0x30, 10 | OBJECT_ID = 0x40, 11 | SECURITY_DESCRIPTOR = 0x50, 12 | VOLUME_NAME = 0x60, 13 | VOLUME_INFORMATION = 0x70, 14 | DATA = 0x80, 15 | INDEX_ROOT = 0x90, 16 | INDEX_ALLOCATION = 0xA0, 17 | BITMAP = 0xB0, 18 | REPARSE_POINT = 0xC0, 19 | EA_INFORMATION = 0xD0, 20 | EA = 0xE0, 21 | LOGGED_UTILITY_STREAM = 0x100, 22 | EndOfAttributes = uint.MaxValue 23 | } 24 | // ReSharper restore InconsistentNaming 25 | } -------------------------------------------------------------------------------- /LineOS/NTFS/Model/Enums/FileEntryFlags.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace LineOS.NTFS.Model.Enums 4 | { 5 | [Flags] 6 | public enum FileEntryFlags : ushort 7 | { 8 | FileInUse = 1, 9 | Directory = 2 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /LineOS/NTFS/Model/Enums/FileNamespace.cs: -------------------------------------------------------------------------------- 1 | namespace LineOS.NTFS.Model.Enums 2 | { 3 | // ReSharper disable InconsistentNaming 4 | public enum FileNamespace : byte 5 | { 6 | POSIX = 0, 7 | Win32 = 1, 8 | DOS = 2, 9 | Win32AndDOS = 3 10 | } 11 | // ReSharper restore InconsistentNaming 12 | } -------------------------------------------------------------------------------- /LineOS/NTFS/Model/Enums/MFTEAFlags.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace LineOS.NTFS.Model.Enums 4 | { 5 | [Flags] 6 | public enum MFTEAFlags 7 | { 8 | NeedEA = 0x80 9 | } 10 | } -------------------------------------------------------------------------------- /LineOS/NTFS/Model/Enums/MFTIndexEntryFlags.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace LineOS.NTFS.Model.Enums 4 | { 5 | [Flags] 6 | public enum MFTIndexEntryFlags : byte 7 | { 8 | SubnodeEntry = 0x01, 9 | LastEntry = 0x02 10 | } 11 | } -------------------------------------------------------------------------------- /LineOS/NTFS/Model/Enums/MFTIndexRootFlags.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace LineOS.NTFS.Model.Enums 4 | { 5 | [Flags] 6 | public enum MFTIndexRootFlags 7 | { 8 | SmallIndex = 0x00, 9 | LargeIndex = 0x01 10 | } 11 | } -------------------------------------------------------------------------------- /LineOS/NTFS/Model/Enums/MetadataMftFiles.cs: -------------------------------------------------------------------------------- 1 | namespace LineOS.NTFS.Model.Enums 2 | { 3 | public enum MetadataMftFiles : uint 4 | { 5 | Mft = 0, 6 | MftMirr = 1, 7 | LogFile = 2, 8 | Volume = 3, 9 | AttrDef = 4, 10 | RootDir = 5, 11 | Bitmap = 6, 12 | Boot = 7, 13 | BadClus = 8, 14 | Secure = 9, 15 | Upcase = 10, 16 | Extend = 11 17 | } 18 | } -------------------------------------------------------------------------------- /LineOS/NTFS/Model/Enums/ResidentFlag.cs: -------------------------------------------------------------------------------- 1 | namespace LineOS.NTFS.Model.Enums 2 | { 3 | public enum ResidentFlag : byte 4 | { 5 | Resident = 0, 6 | NonResident = 1 7 | } 8 | } -------------------------------------------------------------------------------- /LineOS/NTFS/Model/Enums/VolumeInformationFlags.cs: -------------------------------------------------------------------------------- 1 | namespace LineOS.NTFS.Model.Enums 2 | { 3 | // ReSharper disable InconsistentNaming 4 | public enum VolumeInformationFlags : ushort 5 | { 6 | Dirty = 0x0001, 7 | ResizeLogFile = 0x0002, 8 | UpgradeOnMount = 0x0004, 9 | MountedOnNT4 = 0x0008, 10 | DeleteUSNUnderway = 0x0010, 11 | RepairObjectIds = 0x0020, 12 | ModifiedByChkdsk = 0x8000 13 | } 14 | // ReSharper restore InconsistentNaming 15 | } -------------------------------------------------------------------------------- /LineOS/NTFS/Model/FileRecord.cs: -------------------------------------------------------------------------------- 1 |  2 | using System; 3 | using System.Collections.Generic; 4 | using System.Collections.ObjectModel; 5 | using System.Text; 6 | using LineOS.NTFS.Model.Attributes; 7 | using LineOS.NTFS.Model.Enums; 8 | using LineOS.NTFS.Utility; 9 | using Attribute = LineOS.NTFS.Model.Attributes.Attribute; 10 | 11 | namespace LineOS.NTFS.Model 12 | { 13 | public class FileRecord 14 | { 15 | private List _attributes; 16 | private List _externalAttributes; 17 | 18 | public string Signature { get; set; } 19 | public ushort OffsetToUSN { get; set; } 20 | public ushort USNSizeWords { get; set; } 21 | public ulong LogFileUSN { get; set; } 22 | public ushort SequenceNumber { get; set; } 23 | public short HardlinkCount { get; set; } 24 | public ushort OffsetToFirstAttribute { get; set; } 25 | public FileEntryFlags Flags { get; set; } 26 | public uint SizeOfFileRecord { get; set; } 27 | public uint SizeOfFileRecordAllocated { get; set; } 28 | public FileReference BaseFile { get; set; } 29 | public ushort NextFreeAttributeId { get; set; } 30 | public uint MFTNumber { get; set; } 31 | public byte[] USNNumber { get; set; } 32 | public byte[] USNData { get; set; } 33 | 34 | public FileReference FileReference { get; set; } 35 | 36 | public ReadOnlyCollection Attributes => _attributes.AsReadOnly(); 37 | 38 | public ReadOnlyCollection ExternalAttributes => _externalAttributes.AsReadOnly(); 39 | 40 | public static uint ParseAllocatedSize(byte[] data, int offset) 41 | { 42 | return BitConverter.ToUInt32(data, offset + 28); 43 | } 44 | 45 | public bool IsExtensionRecord => BaseFile.RawId != 0; 46 | 47 | public static FileRecord Parse(byte[] data, int offset, ushort bytesPrSector, uint sectors) 48 | { 49 | uint length = bytesPrSector * sectors; 50 | // Debug.Assert(data.Length - offset >= length); 51 | // Debug.Assert(length >= 50); 52 | // Debug.Assert(bytesPrSector % 512 == 0 && bytesPrSector > 0); 53 | // Debug.Assert(sectors > 0); 54 | 55 | FileRecord res = new FileRecord(); 56 | 57 | res.Signature = Encoding.ASCII.GetString(data, offset + 0, 4); 58 | // Debug.Assert(res.Signature == "FILE"); 59 | 60 | res.OffsetToUSN = BitConverter.ToUInt16(data, offset + 4); 61 | res.USNSizeWords = BitConverter.ToUInt16(data, offset + 6); 62 | res.LogFileUSN = BitConverter.ToUInt64(data, offset + 8); 63 | res.SequenceNumber = BitConverter.ToUInt16(data, offset + 16); 64 | res.HardlinkCount = BitConverter.ToInt16(data, offset + 18); 65 | res.OffsetToFirstAttribute = BitConverter.ToUInt16(data, offset + 20); 66 | res.Flags = (FileEntryFlags)BitConverter.ToUInt16(data, offset + 22); 67 | res.SizeOfFileRecord = BitConverter.ToUInt32(data, offset + 24); 68 | res.SizeOfFileRecordAllocated = BitConverter.ToUInt32(data, offset + 28); 69 | res.BaseFile = new FileReference(BitConverter.ToUInt64(data, offset + 32)); 70 | res.NextFreeAttributeId = BitConverter.ToUInt16(data, offset + 40); 71 | // Two unused bytes here 72 | res.MFTNumber = BitConverter.ToUInt32(data, offset + 44); 73 | 74 | res.USNNumber = new byte[2]; 75 | Array.Copy(data, offset + res.OffsetToUSN, res.USNNumber, 0, 2); 76 | 77 | // Debug.Assert(data.Length - offset >= res.OffsetToUSN + 2 + res.USNSizeWords * 2); 78 | 79 | res.USNData = new byte[res.USNSizeWords * 2 - 2]; 80 | Array.Copy(data, offset + res.OffsetToUSN + 2, res.USNData, 0, res.USNData.Length); 81 | 82 | res.FileReference = new FileReference(res.MFTNumber, res.SequenceNumber); 83 | 84 | // Apply the USN Patch 85 | NtfsUtils.ApplyUSNPatch(data, offset, sectors, bytesPrSector, res.USNNumber, res.USNData); 86 | 87 | res._attributes = new List(); 88 | res._externalAttributes = new List(); 89 | 90 | // Parse attributes 91 | res.ParseAttributes(data, res.SizeOfFileRecord - res.OffsetToFirstAttribute, 92 | offset + res.OffsetToFirstAttribute); 93 | 94 | return res; 95 | } 96 | 97 | private void ParseAttributes(byte[] data, uint maxLength, int offset) 98 | { 99 | // Debug.Assert(Signature == "FILE"); 100 | 101 | int attribOffset = offset; 102 | for (int attribId = 0; ; attribId++) 103 | { 104 | AttributeType attributeType = Attribute.GetType(data, attribOffset); 105 | if (attributeType == AttributeType.EndOfAttributes) 106 | break; 107 | 108 | uint length = Attribute.GetTotalLength(data, attribOffset); 109 | 110 | // Debug.Assert(attribOffset + length <= offset + maxLength); 111 | 112 | Attribute attrib = Attribute.ParseSingleAttribute(data, (int)length, attribOffset); 113 | attrib.OwningRecord = FileReference; 114 | _attributes.Add(attrib); 115 | 116 | attribOffset += attrib.TotalLength; 117 | } 118 | } 119 | 120 | public void ParseExternalAttributes(FileRecord record) 121 | { 122 | foreach (var att in Attributes) 123 | { 124 | if (!(att is AttributeList list)) continue; 125 | 126 | foreach (var otherAtt in record.Attributes) 127 | foreach (var itm in list.Items) 128 | if (itm.AttributeId == otherAtt.Id && itm.BaseFile == otherAtt.OwningRecord) 129 | { 130 | _externalAttributes.Add(otherAtt); 131 | break; 132 | } 133 | } 134 | } 135 | } 136 | 137 | } 138 | -------------------------------------------------------------------------------- /LineOS/NTFS/Model/FileReference.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace LineOS.NTFS.Model 4 | { 5 | public class FileReference : IEquatable, IComparable 6 | { 7 | public ulong RawId { get; set; } 8 | public uint FileId { get; set; } 9 | public ushort FileSequenceNumber { get; set; } 10 | 11 | public FileReference(ulong rawId) 12 | { 13 | FileSequenceNumber = (ushort)(rawId >> 48); // Get the high-order 16 bites 14 | FileId = (uint)(rawId & 0xFFFFFFFFUL); // Get the low-order 32 bits 15 | 16 | ushort middleSpace = (ushort)((rawId >> 32) & 0xFFFFUL); // Get the 16 bits in-between the Id and the SequenceNumber 17 | //// Debug.Assert(middleSpace == 0); 18 | 19 | RawId = rawId; 20 | } 21 | 22 | public FileReference(uint fileId, ushort sequenceNumber) 23 | { 24 | FileSequenceNumber = sequenceNumber; 25 | FileId = fileId; 26 | RawId = ((ulong)sequenceNumber << 48) | fileId; 27 | } 28 | 29 | public override string ToString() 30 | { 31 | return FileId + ":" + FileSequenceNumber; 32 | } 33 | 34 | public bool Equals(FileReference other) 35 | { 36 | return this == other; 37 | } 38 | 39 | public override bool Equals(object obj) 40 | { 41 | return Equals(obj as FileReference); 42 | } 43 | 44 | public override int GetHashCode() 45 | { 46 | return RawId.GetHashCode(); 47 | } 48 | 49 | public static bool operator ==(FileReference a, FileReference b) 50 | { 51 | if (ReferenceEquals(a, b)) 52 | return true; 53 | if ((object)a == null || (object)b == null) 54 | return false; 55 | return a.RawId == b.RawId; 56 | } 57 | 58 | public static bool operator !=(FileReference a, FileReference b) 59 | { 60 | return !(a == b); 61 | } 62 | 63 | public static bool operator <(FileReference a, FileReference b) 64 | { 65 | if (ReferenceEquals(a, b)) 66 | return false; 67 | if ((object)a == null) 68 | throw new ArgumentNullException("a"); 69 | if ((object)b == null) 70 | throw new ArgumentNullException("b"); 71 | 72 | return CompareToInternal(a, b) < 0; 73 | } 74 | 75 | public static bool operator >(FileReference a, FileReference b) 76 | { 77 | if (ReferenceEquals(a, b)) 78 | return false; 79 | if ((object)a == null) 80 | throw new ArgumentNullException("a"); 81 | if ((object)b == null) 82 | throw new ArgumentNullException("b"); 83 | 84 | return CompareToInternal(a, b) > 0; 85 | } 86 | 87 | public static bool operator <=(FileReference a, FileReference b) 88 | { 89 | if (ReferenceEquals(a, b)) 90 | return true; 91 | if ((object)a == null) 92 | throw new ArgumentNullException("a"); 93 | if ((object)b == null) 94 | throw new ArgumentNullException("b"); 95 | 96 | return CompareToInternal(a, b) <= 0; 97 | } 98 | 99 | public static bool operator >=(FileReference a, FileReference b) 100 | { 101 | if (ReferenceEquals(a, b)) 102 | return true; 103 | if ((object)a == null) 104 | throw new ArgumentNullException("a"); 105 | if ((object)b == null) 106 | throw new ArgumentNullException("b"); 107 | 108 | return CompareToInternal(a, b) >= 0; 109 | } 110 | 111 | public int CompareTo(FileReference other) 112 | { 113 | // <0 This < other 114 | // 0 This == other 115 | // >0 This > other 116 | 117 | if ((object)other == null) 118 | return 1; 119 | 120 | return CompareToInternal(this, other); 121 | } 122 | 123 | private static int CompareToInternal(FileReference a, FileReference b) 124 | { 125 | // <0 a < b 126 | // 0 a == b 127 | // >0 a > b 128 | 129 | if (a.FileId == b.FileId) 130 | { 131 | // Compare sequence numbers 132 | if (a.FileSequenceNumber < b.FileSequenceNumber) 133 | return -1; 134 | if (a.FileSequenceNumber > b.FileSequenceNumber) 135 | return 1; 136 | 137 | // Both Id and Seq num are identical 138 | return 0; 139 | } 140 | 141 | if (a.FileId < b.FileId) 142 | return -1; 143 | 144 | // FileId > other.FileId 145 | return 1; 146 | } 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /LineOS/NTFS/Model/Headers/AttributeNonResidentHeader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using LineOS.NTFS.Utility; 4 | 5 | namespace LineOS.NTFS.Model.Headers 6 | { 7 | public class AttributeNonResidentHeader : ISaveableObject 8 | { 9 | public long StartingVCN { get; set; } 10 | public long EndingVCN { get; set; } 11 | public ushort ListOffset { get; set; } 12 | public ushort CompressionUnitSize { get; set; } 13 | public ulong ContentSizeAllocated { get; set; } 14 | public ulong ContentSize { get; set; } 15 | public ulong ContentSizeInitialized { get; set; } 16 | public ulong ContentSizeCompressed { get; set; } 17 | public List Fragments { get; set; } 18 | 19 | public static AttributeNonResidentHeader ParseHeader(byte[] data, int offset = 0) 20 | { 21 | // Debug.Assert(data.Length - offset >= 48); 22 | // Debug.Assert(offset >= 0); 23 | 24 | AttributeNonResidentHeader res = new AttributeNonResidentHeader(); 25 | 26 | res.StartingVCN = BitConverter.ToInt64(data, offset); 27 | res.EndingVCN = BitConverter.ToInt64(data, offset + 8); 28 | res.ListOffset = BitConverter.ToUInt16(data, offset + 16); 29 | res.CompressionUnitSize = BitConverter.ToUInt16(data, offset + 18); 30 | res.ContentSizeAllocated = BitConverter.ToUInt64(data, offset + 24); 31 | res.ContentSize = BitConverter.ToUInt64(data, offset + 32); 32 | res.ContentSizeInitialized = BitConverter.ToUInt64(data, offset + 40); 33 | 34 | if (res.CompressionUnitSize != 0) 35 | { 36 | // Debug.Assert(data.Length - offset >= 56); 37 | res.ContentSizeCompressed = BitConverter.ToUInt64(data, offset + 48); 38 | } 39 | 40 | return res; 41 | } 42 | 43 | public int GetSaveLength() 44 | { 45 | int size = ContentSizeCompressed != 0 ? 56 : 48; 46 | 47 | if (Fragments != null) 48 | { 49 | size += DataFragment.GetSaveLength(Fragments); 50 | } 51 | 52 | return size; 53 | } 54 | 55 | public void Save(byte[] buffer, int offset) 56 | { 57 | // Debug.Assert(buffer.Length - offset >= GetSaveLength()); 58 | 59 | LittleEndianConverter.GetBytes(buffer, offset, StartingVCN); 60 | LittleEndianConverter.GetBytes(buffer, offset + 8, EndingVCN); 61 | LittleEndianConverter.GetBytes(buffer, offset + 16, ListOffset); 62 | LittleEndianConverter.GetBytes(buffer, offset + 18, CompressionUnitSize); 63 | LittleEndianConverter.GetBytes(buffer, offset + 24, ContentSizeAllocated); 64 | LittleEndianConverter.GetBytes(buffer, offset + 32, ContentSize); 65 | LittleEndianConverter.GetBytes(buffer, offset + 40, ContentSizeInitialized); 66 | 67 | if (CompressionUnitSize != 0) 68 | LittleEndianConverter.GetBytes(buffer, offset + 48, ContentSizeCompressed); 69 | 70 | if (Fragments != null) 71 | { 72 | int pointer = ContentSizeCompressed != 0 ? 56 : 48; 73 | 74 | DataFragment.Save(buffer, pointer, Fragments); 75 | } 76 | } 77 | } 78 | } -------------------------------------------------------------------------------- /LineOS/NTFS/Model/Headers/AttributeResidentHeader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using LineOS.NTFS.Utility; 3 | 4 | namespace LineOS.NTFS.Model.Headers 5 | { 6 | public class AttributeResidentHeader : ISaveableObject 7 | { 8 | public uint ContentLength { get; set; } 9 | public ushort ContentOffset { get; set; } 10 | 11 | public static AttributeResidentHeader ParseHeader(byte[] data, int offset = 0) 12 | { 13 | // Debug.Assert(data.Length - offset >= 6); 14 | // Debug.Assert(offset >= 0); 15 | 16 | AttributeResidentHeader res = new AttributeResidentHeader(); 17 | 18 | res.ContentLength = BitConverter.ToUInt32(data, offset); 19 | res.ContentOffset = BitConverter.ToUInt16(data, offset + 4); 20 | 21 | return res; 22 | } 23 | 24 | public int GetSaveLength() 25 | { 26 | return 8; 27 | } 28 | 29 | public void Save(byte[] buffer, int offset) 30 | { 31 | // Debug.Assert(buffer.Length - offset >= GetSaveLength()); 32 | 33 | LittleEndianConverter.GetBytes(buffer, offset, ContentLength); 34 | LittleEndianConverter.GetBytes(buffer, offset + 4, ContentOffset); 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /LineOS/NTFS/Model/ISaveableObject.cs: -------------------------------------------------------------------------------- 1 | namespace LineOS.NTFS.Model 2 | { 3 | public interface ISaveableObject 4 | { 5 | int GetSaveLength(); 6 | void Save(byte[] buffer, int offset); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /LineOS/NTFS/Model/IndexAllocationChunk.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | using System.Text; 5 | using LineOS.NTFS.Model.Enums; 6 | using LineOS.NTFS.Utility; 7 | 8 | namespace LineOS.NTFS.Model 9 | { 10 | public class IndexAllocationChunk 11 | { 12 | public string Signature { get; set; } 13 | public ushort OffsetToUSN { get; set; } 14 | public ushort USNSizeWords { get; set; } 15 | public ulong LogFileUSN { get; set; } 16 | public ulong VCNInIndexAllocation { get; set; } 17 | public uint OffsetToFirstIndex { get; set; } 18 | public uint SizeOfIndexTotal { get; set; } 19 | public uint SizeOfIndexAllocated { get; set; } 20 | public byte HasChildren { get; set; } 21 | public byte[] USNNumber { get; set; } 22 | public byte[] USNData { get; set; } 23 | 24 | public List Entries { get; set; } 25 | 26 | public static bool IsIndexAllocationChunk(byte[] data, int offset) 27 | { 28 | // Debug.Assert(data.Length - offset >= 4); 29 | string signature = Encoding.ASCII.GetString(data, offset + 0, 4); 30 | 31 | return signature == "INDX"; 32 | } 33 | 34 | public static IndexAllocationChunk ParseBody(Ntfs ntfsInfo, byte[] data, int offset) 35 | { 36 | // Debug.Assert(data.Length >= 36); 37 | 38 | IndexAllocationChunk res = new IndexAllocationChunk(); 39 | 40 | // Parse 41 | res.Signature = Encoding.ASCII.GetString(data, offset + 0, 4); 42 | 43 | // Debug.Assert(res.Signature == "INDX"); 44 | 45 | res.OffsetToUSN = BitConverter.ToUInt16(data, offset + 4); 46 | res.USNSizeWords = BitConverter.ToUInt16(data, offset + 6); 47 | res.LogFileUSN = BitConverter.ToUInt64(data, offset + 8); 48 | res.VCNInIndexAllocation = BitConverter.ToUInt64(data, offset + 16); 49 | res.OffsetToFirstIndex = BitConverter.ToUInt32(data, offset + 24); 50 | res.SizeOfIndexTotal = BitConverter.ToUInt32(data, offset + 28); 51 | res.SizeOfIndexAllocated = BitConverter.ToUInt32(data, offset + 32); 52 | res.HasChildren = data[36]; 53 | 54 | // Debug.Assert(data.Length >= offset + res.OffsetToUSN + 2 + res.USNSizeWords * 2); 55 | 56 | res.USNNumber = new byte[2]; 57 | Array.Copy(data, offset + res.OffsetToUSN, res.USNNumber, 0, 2); 58 | 59 | res.USNData = new byte[res.USNSizeWords * 2 - 2]; 60 | Array.Copy(data, offset + res.OffsetToUSN + 2, res.USNData, 0, res.USNData.Length); 61 | 62 | // Patch USN Data 63 | NtfsUtils.ApplyUSNPatch(data, offset, (res.SizeOfIndexAllocated + 24) / ntfsInfo.BytesPerSector, (ushort)ntfsInfo.BytesPerSector, res.USNNumber, res.USNData); 64 | 65 | // Debug.Assert(offset + res.SizeOfIndexTotal <= data.Length); 66 | 67 | // Parse entries 68 | List entries = new List(); 69 | 70 | int pointer = offset + (int)(res.OffsetToFirstIndex + 24); // Offset is relative to 0x18 71 | while (pointer <= offset + res.SizeOfIndexTotal + 24) 72 | { 73 | IndexEntry entry = IndexEntry.ParseData(data, offset + (int)res.SizeOfIndexTotal - pointer + 24, pointer); 74 | 75 | if ((entry.Flags & MFTIndexEntryFlags.LastEntry) != 0) 76 | break; 77 | 78 | entries.Add(entry); 79 | 80 | pointer += entry.Size; 81 | } 82 | 83 | res.Entries = entries; 84 | 85 | return res; 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /LineOS/NTFS/Model/IndexEntry.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using LineOS.NTFS.Model.Attributes; 3 | using LineOS.NTFS.Model.Enums; 4 | using LineOS.NTFS.Model.Headers; 5 | 6 | namespace LineOS.NTFS.Model 7 | { 8 | public class IndexEntry 9 | { 10 | public FileReference FileRefence { get; set; } 11 | public ushort Size { get; set; } 12 | public ushort StreamSize { get; set; } 13 | public MFTIndexEntryFlags Flags { get; set; } 14 | public byte[] Stream { get; set; } 15 | 16 | public AttributeFileName ChildFileName { get; set; } 17 | 18 | public static IndexEntry ParseData(byte[] data, int maxLength, int offset) 19 | { 20 | // Debug.Assert(maxLength >= 16); 21 | 22 | IndexEntry res = new IndexEntry(); 23 | 24 | res.FileRefence = new FileReference(BitConverter.ToUInt64(data, offset)); 25 | res.Size = BitConverter.ToUInt16(data, offset + 8); 26 | res.StreamSize = BitConverter.ToUInt16(data, offset + 10); 27 | res.Flags = (MFTIndexEntryFlags)data[offset + 12]; 28 | 29 | // Debug.Assert(maxLength >= res.Size); 30 | // Debug.Assert(maxLength >= 16 + res.StreamSize); 31 | 32 | res.Stream = new byte[res.StreamSize]; 33 | Array.Copy(data, offset + 16, res.Stream, 0, res.StreamSize); 34 | 35 | 36 | if (res.StreamSize > 66) 37 | { 38 | res.ChildFileName = new AttributeFileName(); 39 | res.ChildFileName.ParseAttributeResidentBody(res.Stream, res.StreamSize, 0); 40 | 41 | // Fake the resident header 42 | res.ChildFileName.ResidentHeader = new AttributeResidentHeader(); 43 | res.ChildFileName.ResidentHeader.ContentLength = res.StreamSize; 44 | res.ChildFileName.ResidentHeader.ContentOffset = 0; 45 | } 46 | 47 | return res; 48 | } 49 | 50 | public override string ToString() 51 | { 52 | if (ChildFileName == null) 53 | return FileRefence.ToString(); 54 | return FileRefence + " (" + ChildFileName.FileName + ")"; 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /LineOS/NTFS/Ntfs.cs: -------------------------------------------------------------------------------- 1 | using LineOS.FS; 2 | using LineOS.NTFS.Parser; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using LineOS.NTFS.IO; 7 | using LineOS.NTFS.Model; 8 | using LineOS.NTFS.Model.Attributes; 9 | using LineOS.NTFS.Model.Enums; 10 | 11 | namespace LineOS.NTFS 12 | { 13 | public class Ntfs 14 | { 15 | /* 16 | * https://github.com/LordMike/NtfsLib/blob/master/NTFSLib/NTFS/NTFSWrapper.cs 17 | * https://github.com/LordMike/NtfsLib/blob/master/NTFSLib/IO/NtfsFileEntry.cs 18 | */ 19 | 20 | public BootSector BootSector { get; set; } 21 | public BlockDeviceStream DiskStream { get; set; } 22 | 23 | public uint BytesPerFileRecord { get; private set; } 24 | public uint FileRecordCount { get; private set; } 25 | public uint BytesPerCluster => (uint)(BootSector.BytesPerSector * BootSector.SectorsPerCluster); 26 | public ushort BytesPerSector => BootSector.BytesPerSector; 27 | public uint SectorsPerRecord { get; private set; } 28 | public uint SectorsPerCluster => BootSector.SectorsPerCluster; 29 | 30 | public FileRecord MftFile { get; private set; } 31 | public Stream MftStream { get; private set; } 32 | 33 | private FileRecord[] FileRecords { get; set; } 34 | 35 | public static Ntfs Create(BlockDeviceStream diskStream) 36 | { 37 | var ntfs = new Ntfs {DiskStream = diskStream}; 38 | ntfs.Initialize(); 39 | return ntfs; 40 | } 41 | 42 | private void Initialize() 43 | { 44 | byte[] data = new byte[512]; 45 | DiskStream.Seek(0, SeekOrigin.Begin); 46 | DiskStream.Read(data, 0, data.Length); 47 | BootSector = BootSector.ParseData(data, 512, 0); 48 | 49 | BytesPerFileRecord = BootSector.MftRecordSizeBytes; 50 | SectorsPerRecord = BytesPerFileRecord / BytesPerSector; 51 | 52 | MftFile = ParseMftRecordData(ReadMftRecordData((uint)MetadataMftFiles.Mft)); 53 | MftStream = OpenFileRecord(MftFile); 54 | 55 | AttributeData mftFileData = null; 56 | foreach (var att in MftFile.Attributes) 57 | if (att is AttributeData attributeData) 58 | mftFileData = attributeData; 59 | 60 | if (mftFileData == null) 61 | throw new Exception("ntfs error: unable to load mft data"); 62 | 63 | long clusterBytes = 0; 64 | foreach (var frag in mftFileData.DataFragments) 65 | clusterBytes += frag.Clusters * BytesPerCluster; 66 | 67 | FileRecordCount = (uint)(clusterBytes / BytesPerFileRecord); 68 | FileRecords = new FileRecord[FileRecordCount]; 69 | FileRecords[0] = MftFile; 70 | 71 | Console.WriteLine("[NTFSDRV2] Initialized with " + FileRecordCount + " file records"); 72 | } 73 | 74 | public FileRecord ReadMftRecord(uint number, bool parseAttributeLists = true) 75 | { 76 | if (number <= FileRecords.Length && FileRecords[number] != null) 77 | return FileRecords[number]; 78 | 79 | var data = ReadMftRecordData(number); 80 | var record = ParseMftRecordData(data); 81 | 82 | FileRecords[number] = record; 83 | 84 | 85 | if (BytesPerFileRecord == 0) 86 | BytesPerFileRecord = record.SizeOfFileRecordAllocated; 87 | 88 | if (parseAttributeLists) 89 | LoadAttributeLists(record); 90 | 91 | return record; 92 | } 93 | 94 | private void LoadAttributeLists(FileRecord record) 95 | { 96 | if (record.ExternalAttributes.Count > 0) 97 | return; 98 | 99 | var completedFiles = new List(); 100 | foreach (var att in record.Attributes) 101 | { 102 | if (!(att is AttributeList listAttr)) continue; 103 | 104 | if(listAttr.NonResidentFlag == ResidentFlag.NonResident) 105 | listAttr.ParseAttributeNonResidentBody(this); 106 | 107 | foreach (var listItem in listAttr.Items) 108 | { 109 | if(listItem.BaseFile.Equals(record.FileReference) || completedFiles.Contains(listItem.BaseFile)) 110 | continue; 111 | completedFiles.Add(listItem.BaseFile); 112 | 113 | var otherRecord = ReadMftRecord(listItem.BaseFile.FileId); 114 | record.ParseExternalAttributes(otherRecord); 115 | } 116 | } 117 | } 118 | 119 | public byte[] ReadMftRecordData(uint number) 120 | { 121 | var length = BytesPerFileRecord == 0 ? 4096 : BytesPerFileRecord; 122 | long offset = number * length; 123 | if (MftFile == null) 124 | offset += (long)(BootSector.MftCluster * BytesPerCluster); 125 | else if (MftStream != null) 126 | { 127 | var mftData = new byte[length]; 128 | MftStream.Seek(offset, SeekOrigin.Begin); 129 | MftStream.Read(mftData, 0, mftData.Length); 130 | return mftData; 131 | } 132 | 133 | var data = new byte[length]; 134 | DiskStream.Seek(offset, SeekOrigin.Begin); 135 | DiskStream.Read(data, 0, data.Length); 136 | return data; 137 | } 138 | 139 | public FileRecord ParseMftRecordData(byte[] data) 140 | { 141 | return FileRecord.Parse(data, 0, BytesPerSector, SectorsPerRecord); 142 | } 143 | 144 | private Stream OpenFileRecord(FileRecord record, string dataStream = "") 145 | { 146 | LoadAttributeLists(record); 147 | 148 | var dataAttribs = new List(); 149 | foreach (var attrib in record.Attributes) 150 | if (attrib is AttributeData data && attrib.AttributeName == dataStream) 151 | dataAttribs.Add(data); 152 | 153 | if (dataAttribs.Count == 1 && dataAttribs[0].NonResidentFlag == ResidentFlag.Resident) 154 | return new MemoryStream(dataAttribs[0].DataBytes); 155 | 156 | var fragments = new List(); 157 | foreach (var attrib in dataAttribs) 158 | foreach (var frag in attrib.DataFragments) 159 | fragments.Add(frag); 160 | 161 | var compressionUnitSize = dataAttribs[0].NonResidentHeader.CompressionUnitSize; 162 | var compressionClusterCount = (ushort)(compressionUnitSize == 0 ? 0 : Math.Pow(2, compressionUnitSize)); 163 | var contentSize = (long)dataAttribs[0].NonResidentHeader.ContentSize; 164 | 165 | return new NtfsDiskStream(DiskStream, false, fragments, BytesPerCluster, compressionClusterCount, contentSize); 166 | } 167 | 168 | public NtfsDirectory GetRootDirectory() 169 | { 170 | return (NtfsDirectory) NtfsFileEntry.CreateEntry(this, (uint) MetadataMftFiles.RootDir); 171 | } 172 | 173 | public void ParseNonResidentAttribute(AttributeIndexAllocation alloc) 174 | { 175 | if(alloc.NonResidentHeader.Fragments.Count > 0) 176 | alloc.ParseAttributeNonResidentBody(this); 177 | } 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /LineOS/NTFS/Parser/BootSector.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | 4 | namespace LineOS.NTFS.Parser 5 | { 6 | /* 7 | * The MIT License (MIT) 8 | * 9 | * Copyright (c) 2015 Michael Bisbjerg 10 | * 11 | * Permission is hereby granted, free of charge, to any person obtaining a copy 12 | * of this software and associated documentation files (the "Software"), to deal 13 | * in the Software without restriction, including without limitation the rights 14 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | * copies of the Software, and to permit persons to whom the Software is 16 | * furnished to do so, subject to the following conditions: 17 | * 18 | * The above copyright notice and this permission notice shall be included in all 19 | * copies or substantial portions of the Software. 20 | * 21 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 27 | * SOFTWARE. 28 | */ 29 | public class BootSector 30 | { 31 | public byte[] JmpInstruction { get; set; } 32 | public string OemCode { get; set; } 33 | public ushort BytesPerSector { get; set; } 34 | public byte SectorsPerCluster { get; set; } 35 | public ushort ReservedSectors { get; set; } 36 | public byte MediaDescriptor { get; set; } 37 | public ushort SectorsPerTrack { get; set; } 38 | public ushort NumberOfHeads { get; set; } 39 | public uint HiddenSectors { get; set; } 40 | public uint Usually80008000 { get; set; } 41 | public ulong TotalSectors { get; set; } 42 | public ulong MftCluster { get; set; } 43 | public ulong MftMirrCluster { get; set; } 44 | public uint MftRecordSizeBytes { get; set; } 45 | public uint MftIndexSizeBytes { get; set; } 46 | public ulong SerialNumber { get; set; } 47 | public uint Checksum { get; set; } 48 | public byte[] BootstrapCode { get; set; } 49 | public byte[] Signature { get; set; } 50 | 51 | public static BootSector ParseData(byte[] data, int maxLength, int offset) 52 | { 53 | //// Debug.Assert(data.Length - offset >= 512); 54 | //// Debug.Assert(0 <= offset && offset <= data.Length); 55 | 56 | BootSector res = new BootSector(); 57 | 58 | res.JmpInstruction = new byte[3]; 59 | Array.Copy(data, offset, res.JmpInstruction, 0, 3); 60 | 61 | res.OemCode = Encoding.ASCII.GetString(data, offset + 3, 8).Trim(); 62 | res.BytesPerSector = BitConverter.ToUInt16(data, offset + 11); 63 | res.SectorsPerCluster = data[offset + 13]; 64 | res.ReservedSectors = BitConverter.ToUInt16(data, offset + 14); 65 | res.MediaDescriptor = data[offset + 21]; 66 | res.SectorsPerTrack = BitConverter.ToUInt16(data, offset + 24); 67 | res.NumberOfHeads = BitConverter.ToUInt16(data, offset + 26); 68 | res.HiddenSectors = BitConverter.ToUInt32(data, offset + 28); 69 | res.Usually80008000 = BitConverter.ToUInt32(data, offset + 36); 70 | res.TotalSectors = BitConverter.ToUInt64(data, offset + 40); 71 | res.MftCluster = BitConverter.ToUInt64(data, offset + 48); 72 | res.MftMirrCluster = BitConverter.ToUInt64(data, offset + 56); 73 | res.MftRecordSizeBytes = BitConverter.ToUInt32(data, offset + 64); 74 | res.MftIndexSizeBytes = BitConverter.ToUInt32(data, offset + 68); 75 | res.SerialNumber = BitConverter.ToUInt64(data, offset + 72); 76 | res.Checksum = BitConverter.ToUInt32(data, offset + 80); 77 | 78 | res.MftRecordSizeBytes = InterpretClusterCount(res.MftRecordSizeBytes); 79 | res.MftIndexSizeBytes = InterpretClusterCount(res.MftRecordSizeBytes); 80 | 81 | res.BootstrapCode = new byte[426]; 82 | Array.Copy(data, offset + 84, res.BootstrapCode, 0, 426); 83 | 84 | res.Signature = new byte[2]; 85 | Array.Copy(data, offset + 510, res.Signature, 0, 2); 86 | 87 | // Signature should always be this 88 | //// Debug.Assert(res.Signature[0] == 0x55); 89 | //// Debug.Assert(res.Signature[1] == 0xAA); 90 | 91 | return res; 92 | } 93 | 94 | private static uint InterpretClusterCount(uint num) 95 | { 96 | // Find if this number is negative, taking into account the number of bytes needed to store it 97 | int bytes = 0; 98 | for (int i = 0; i < 4; i++) 99 | { 100 | if (num >= ((uint)0xFF << (i * 8))) 101 | { 102 | bytes = i + 1; 103 | } 104 | } 105 | 106 | // Is it negative? 107 | uint negativeNum = 0x80; 108 | for (int i = 0; i < bytes; i++) 109 | { 110 | negativeNum = negativeNum << 8; 111 | } 112 | 113 | if ((negativeNum & num) != negativeNum) 114 | // Not negative, return as-is 115 | return num; 116 | 117 | int newNumber = (int)num; 118 | for (int i = bytes + 1; i < 4; i++) 119 | { 120 | newNumber |= 0xFF << (i * 8); 121 | } 122 | 123 | // Calculate count 124 | // 2^(-1 * -10) 125 | uint res = (uint)Math.Pow(2, -newNumber); 126 | 127 | return res; 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /LineOS/NTFS/Parser/FileNamespaceComparer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using LineOS.NTFS.Model.Enums; 3 | 4 | namespace LineOS.NTFS.Parser 5 | { 6 | public class FileNamespaceComparer : IComparer 7 | { 8 | static FileNamespace[] _order = new[] { FileNamespace.Win32, FileNamespace.Win32AndDOS, FileNamespace.POSIX, FileNamespace.DOS }; 9 | 10 | public int Compare(FileNamespace x, FileNamespace y) 11 | { 12 | foreach (FileNamespace fileNamespace in _order) 13 | { 14 | if (x == fileNamespace && y == fileNamespace) 15 | return 0; 16 | 17 | if (x == fileNamespace) 18 | return 1; 19 | 20 | if (y == fileNamespace) 21 | return -1; 22 | } 23 | 24 | return 0; 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /LineOS/NTFS/Parser/NtfsParser.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using LineOS.NTFS.IO; 5 | using LineOS.NTFS.Model; 6 | using LineOS.NTFS.Model.Attributes; 7 | using LineOS.NTFS.Model.Enums; 8 | 9 | namespace LineOS.NTFS.Parser 10 | { 11 | public class NtfsParser 12 | { 13 | public BootSector BootSector { get; private set; } 14 | 15 | public uint SectorsPerRecord { get; private set; } 16 | 17 | public uint BytesPerFileRecord { get; set; } 18 | public uint BytesPerCluster => (uint)(BootSector.BytesPerSector * BootSector.SectorsPerCluster); 19 | public uint BytesPerSector => BootSector.BytesPerSector; 20 | public byte SectorsPerCluster => BootSector.SectorsPerCluster; 21 | public ulong TotalSectors => BootSector.TotalSectors; 22 | public ulong TotalClusters => BootSector.TotalSectors / BootSector.SectorsPerCluster; 23 | 24 | public uint CurrentMftRecordNumber { get; set; } 25 | public uint FileRecordCount { get; private set; } 26 | 27 | public bool OwnsDiskStream => false; 28 | public Stream DiskStream { get; } 29 | 30 | private FileRecord mftRecord; 31 | private NtfsDiskStream mftStream; 32 | 33 | private byte[] buffer; 34 | 35 | 36 | public NtfsParser(Stream diskStream) 37 | { 38 | this.DiskStream = diskStream; 39 | ParseBootSector(); 40 | ParseMft(); 41 | } 42 | 43 | private void ParseBootSector() 44 | { 45 | var data = new byte[512]; 46 | DiskStream.Seek(0, SeekOrigin.Begin); 47 | DiskStream.Read(data, 0, data.Length); 48 | 49 | BootSector = BootSector.ParseData(data, data.Length, 0); 50 | BytesPerFileRecord = BootSector.MftRecordSizeBytes; 51 | SectorsPerRecord = BootSector.MftRecordSizeBytes / BootSector.BytesPerSector; 52 | } 53 | 54 | private void ParseMft() 55 | { 56 | buffer = new byte[BytesPerFileRecord]; 57 | DiskStream.Seek((long)(BootSector.MftCluster * BytesPerCluster), SeekOrigin.Begin); 58 | DiskStream.Read(buffer, 0, buffer.Length); 59 | mftRecord = FileRecord.Parse(buffer, 0, BootSector.BytesPerSector, SectorsPerRecord); 60 | 61 | var fragmentList = new List(); 62 | AttributeData firstAttributeData = null; 63 | foreach (var attribute in mftRecord.Attributes) 64 | if (attribute is AttributeData data && data.AttributeName == string.Empty && 65 | data.NonResidentFlag == ResidentFlag.NonResident) 66 | { 67 | if (firstAttributeData == null) 68 | firstAttributeData = data; 69 | foreach (var frag in data.DataFragments) 70 | fragmentList.Add(frag); 71 | } 72 | 73 | fragmentList = Utility.Util.Sort(fragmentList, new DataFragmentComparer()); 74 | ushort compressionUnitSize = firstAttributeData.NonResidentHeader.CompressionUnitSize; 75 | ushort compressionClusterCount = (ushort)(compressionUnitSize == 0 ? 0 : Math.Pow(2, compressionUnitSize)); 76 | mftStream = new NtfsDiskStream(DiskStream, false, fragmentList, BytesPerCluster, compressionClusterCount, (long)firstAttributeData.NonResidentHeader.ContentSize); 77 | 78 | CurrentMftRecordNumber = 0; 79 | FileRecordCount = (uint)(mftStream.Length / BytesPerFileRecord); 80 | Console.WriteLine("[NTFS DRIVER] " + FileRecordCount + " file records found"); 81 | } 82 | 83 | public FileRecord NextMFTRecord() 84 | { 85 | var newPosition = CurrentMftRecordNumber * BytesPerFileRecord; 86 | if (mftStream.Position != newPosition) 87 | mftStream.Seek(newPosition, SeekOrigin.Begin); 88 | var read = mftStream.Read(buffer, 0, buffer.Length); 89 | 90 | if (read == 0) 91 | return null; 92 | 93 | var record = FileRecord.Parse(buffer, 0, BootSector.BytesPerSector, SectorsPerRecord); 94 | CurrentMftRecordNumber = record.MFTNumber + 1; 95 | return record; 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /LineOS/NTFS/Utility/CompatDictionary.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | 5 | namespace LineOS.NTFS.Utility 6 | { 7 | public class CompatDictionary : IDictionary 8 | { 9 | private List> list = new List>(); 10 | 11 | public IEnumerator> GetEnumerator() 12 | { 13 | return list.GetEnumerator(); 14 | } 15 | 16 | IEnumerator IEnumerable.GetEnumerator() 17 | { 18 | return GetEnumerator(); 19 | } 20 | 21 | public void Add(KeyValuePair item) 22 | { 23 | list.Add(item); 24 | } 25 | 26 | public void Clear() 27 | { 28 | list.Clear(); 29 | } 30 | 31 | public bool Contains(KeyValuePair item) 32 | { 33 | return list.Contains(item); 34 | } 35 | 36 | public void CopyTo(KeyValuePair[] array, int arrayIndex) 37 | { 38 | throw new NotImplementedException(); 39 | } 40 | 41 | public bool Remove(KeyValuePair item) 42 | { 43 | return list.Remove(item); 44 | } 45 | 46 | public int Count => list.Count; 47 | public bool IsReadOnly => false; 48 | public void Add(TKey key, TValue value) 49 | { 50 | list.Add(new KeyValuePair(key, value)); 51 | } 52 | 53 | public bool ContainsKey(TKey key) 54 | { 55 | throw new NotImplementedException(); 56 | } 57 | 58 | public bool Remove(TKey key) 59 | { 60 | KeyValuePair? itm2remove = null; 61 | foreach (var kvp in list) 62 | if (kvp.Key.Equals(key)) 63 | { 64 | itm2remove = kvp; 65 | break; 66 | } 67 | 68 | if (itm2remove.HasValue) 69 | { 70 | return list.Remove(itm2remove.Value); 71 | } 72 | else return false; 73 | } 74 | 75 | public bool TryGetValue(TKey key, out TValue value) 76 | { 77 | foreach (var kvp in list) 78 | if (kvp.Key.Equals(key)) 79 | { 80 | value = kvp.Value; 81 | return true; 82 | } 83 | 84 | value = default(TValue); 85 | return false; 86 | } 87 | 88 | public TValue this[TKey key] 89 | { 90 | get 91 | { 92 | foreach (var kvp in list) 93 | if (kvp.Key.Equals(key)) 94 | return kvp.Value; 95 | throw new Exception("no such key"); 96 | } 97 | set 98 | { 99 | for (int i = 0; i < list.Count; i++) 100 | { 101 | var kvp = list[i]; 102 | if (kvp.Key.Equals(key)) 103 | list[i] = new KeyValuePair(kvp.Key, value); 104 | } 105 | 106 | } 107 | } 108 | 109 | public ICollection Keys { get; } 110 | 111 | public ICollection Values 112 | { 113 | get 114 | { 115 | List values = new List(); 116 | foreach(var v in list) 117 | values.Add(v.Value); 118 | return values; 119 | } 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /LineOS/NTFS/Utility/LittleEndianConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace LineOS.NTFS.Utility 4 | { 5 | public static class LittleEndianConverter 6 | { 7 | public static void GetBytes(byte[] buffer, int offset, short value) 8 | { 9 | buffer[offset + 0] = (byte)((value >> 0) & 0xFF); 10 | buffer[offset + 1] = (byte)((value >> 8) & 0xFF); 11 | } 12 | 13 | public static void GetBytes(byte[] buffer, int offset, ushort value) 14 | { 15 | buffer[offset + 0] = (byte)((value >> 0) & 0xFF); 16 | buffer[offset + 1] = (byte)((value >> 8) & 0xFF); 17 | } 18 | 19 | public static void GetBytes(byte[] buffer, int offset, int value) 20 | { 21 | buffer[offset + 0] = (byte)((value >> 0) & 0xFF); 22 | buffer[offset + 1] = (byte)((value >> 8) & 0xFF); 23 | buffer[offset + 2] = (byte)((value >> 16) & 0xFF); 24 | buffer[offset + 3] = (byte)((value >> 24) & 0xFF); 25 | } 26 | 27 | public static void GetBytes(byte[] buffer, int offset, uint value) 28 | { 29 | buffer[offset + 0] = (byte)((value >> 0) & 0xFF); 30 | buffer[offset + 1] = (byte)((value >> 8) & 0xFF); 31 | buffer[offset + 2] = (byte)((value >> 16) & 0xFF); 32 | buffer[offset + 3] = (byte)((value >> 24) & 0xFF); 33 | } 34 | 35 | public static void GetBytes(byte[] buffer, int offset, long value) 36 | { 37 | buffer[offset + 0] = (byte)((value >> 0) & 0xFF); 38 | buffer[offset + 1] = (byte)((value >> 8) & 0xFF); 39 | buffer[offset + 2] = (byte)((value >> 16) & 0xFF); 40 | buffer[offset + 3] = (byte)((value >> 24) & 0xFF); 41 | buffer[offset + 4] = (byte)((value >> 32) & 0xFF); 42 | buffer[offset + 5] = (byte)((value >> 40) & 0xFF); 43 | buffer[offset + 6] = (byte)((value >> 48) & 0xFF); 44 | buffer[offset + 7] = (byte)((value >> 56) & 0xFF); 45 | } 46 | 47 | public static void GetBytes(byte[] buffer, int offset, ulong value) 48 | { 49 | buffer[offset + 0] = (byte)((value >> 0) & 0xFF); 50 | buffer[offset + 1] = (byte)((value >> 8) & 0xFF); 51 | buffer[offset + 2] = (byte)((value >> 16) & 0xFF); 52 | buffer[offset + 3] = (byte)((value >> 24) & 0xFF); 53 | buffer[offset + 4] = (byte)((value >> 32) & 0xFF); 54 | buffer[offset + 5] = (byte)((value >> 40) & 0xFF); 55 | buffer[offset + 6] = (byte)((value >> 48) & 0xFF); 56 | buffer[offset + 7] = (byte)((value >> 56) & 0xFF); 57 | } 58 | 59 | public static void GetBytes(byte[] buffer, int offset, DateTime value, DatetimeBinaryFormat format = DatetimeBinaryFormat.WinFileTime) 60 | { 61 | switch (format) 62 | { 63 | case DatetimeBinaryFormat.WinFileTime: 64 | NtfsUtils.ToWinFileTime(buffer, offset, value); 65 | break; 66 | default: 67 | throw new ArgumentOutOfRangeException("format"); 68 | } 69 | } 70 | 71 | } 72 | 73 | public enum DatetimeBinaryFormat 74 | { 75 | WinFileTime 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /LineOS/NTFS/Utility/NtfsUtil.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using LineOS.NTFS.IO; 5 | using LineOS.NTFS.Model; 6 | using LineOS.NTFS.Model.Attributes; 7 | using LineOS.NTFS.Model.Enums; 8 | 9 | namespace LineOS.NTFS.Utility 10 | { 11 | public static class NtfsUtils 12 | { 13 | 14 | private static readonly long MaxFileTime = 9999; // DateTime.MaxValue.ToFileTimeUtc(); 15 | 16 | public static DateTime FromWinFileTime(byte[] data, int offset) 17 | { 18 | /*long fileTime = BitConverter.ToInt64(data, offset); 19 | 20 | if (fileTime >= MaxFileTime) 21 | return DateTime.MaxValue;*/ 22 | 23 | return DateTime.MaxValue; 24 | } 25 | 26 | public static void ToWinFileTime(byte[] data, int offset, DateTime dateTime) 27 | { 28 | if (dateTime == DateTime.MaxValue) 29 | { 30 | LittleEndianConverter.GetBytes(data, offset, long.MaxValue); 31 | } 32 | else 33 | { 34 | long fileTime = 0; // dateTime.ToFileTimeUtc(); 35 | 36 | LittleEndianConverter.GetBytes(data, offset, fileTime); 37 | } 38 | } 39 | 40 | public static byte[] ReadFragments(Ntfs ntfsInfo, List fragments) 41 | { 42 | long totalLength = 0; 43 | foreach (var frag in fragments) 44 | totalLength += frag.Clusters * ntfsInfo.BytesPerCluster; 45 | 46 | var data = new byte[totalLength]; 47 | 48 | Stream diskStream = ntfsInfo.DiskStream; 49 | using (NtfsDiskStream stream = new NtfsDiskStream(diskStream, false, fragments, ntfsInfo.BytesPerCluster, 0, totalLength)) 50 | { 51 | stream.Read(data, 0, data.Length); 52 | } 53 | 54 | // Return the data 55 | return data; 56 | } 57 | 58 | public static void ApplyUSNPatch(byte[] data, int offset, uint sectors, ushort bytesPrSector, byte[] usnNumber, byte[] usnData) 59 | { 60 | // // Debug.Assert(data.Length >= offset + sectors * bytesPrSector); 61 | // // Debug.Assert(usnNumber.Length == 2); 62 | // // Debug.Assert(sectors * 2 <= usnData.Length); 63 | 64 | for (int i = 0; i < sectors; i++) 65 | { 66 | // Get pointer to the last two bytes 67 | int blockOffset = offset + i * bytesPrSector + 510; 68 | 69 | // Check that they match the USN Number 70 | // // Debug.Assert(data[blockOffset] == usnNumber[0]); 71 | // // Debug.Assert(data[blockOffset + 1] == usnNumber[1]); 72 | 73 | // Patch in new data 74 | data[blockOffset] = usnData[i * 2]; 75 | data[blockOffset + 1] = usnData[i * 2 + 1]; 76 | } 77 | } 78 | public static AttributeFileName GetPreferredDisplayName(FileRecord record) 79 | { 80 | AttributeFileName posix = null; 81 | AttributeFileName win32 = null; 82 | AttributeFileName dos = null; 83 | AttributeFileName win32AndDos = null; 84 | foreach (var a in record.Attributes) 85 | if (a is AttributeFileName fileName) 86 | switch (fileName.FilenameNamespace) 87 | { 88 | case FileNamespace.POSIX: 89 | posix = fileName; 90 | break; 91 | case FileNamespace.DOS: 92 | dos = fileName; 93 | break; 94 | case FileNamespace.Win32: 95 | win32 = fileName; 96 | break; 97 | case FileNamespace.Win32AndDOS: 98 | win32AndDos = fileName; 99 | break; 100 | } 101 | 102 | if (win32 != null) 103 | return win32; 104 | if (win32AndDos != null) 105 | return win32AndDos; 106 | if (posix != null) 107 | return posix; 108 | if (dos != null) 109 | return dos; 110 | 111 | throw new Exception("ntfs: invalid filename attribute"); 112 | } 113 | 114 | /*public static AttributeFileName GetPreferredDisplayName(FileRecord record) 115 | { 116 | return GetPreferredDisplayName(record.Attributes); 117 | } 118 | 119 | public static AttributeFileName GetPreferredDisplayName(IEnumerable attributes) 120 | { 121 | return attributes.OfType().OrderByDescending(s => s.FilenameNamespace, new FileNamespaceComparer()).FirstOrDefault(); 122 | }*/ 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /LineOS/NTFS/Utility/Util.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace LineOS.NTFS.Utility 4 | { 5 | public class Util 6 | { 7 | 8 | public static List Sort(List t, IComparer comparer) 9 | { 10 | var array = t.ToArray(); 11 | Quicksort(array, 0, array.Length - 1, comparer); 12 | for (int i = 0; i < array.Length; i++) 13 | t[i] = array[i]; 14 | return t; 15 | } 16 | 17 | private static void Quicksort(T[] data, int left, int right, IComparer comparer) 18 | { 19 | int i, j; 20 | T pivot, temp; 21 | i = left; 22 | j = right; 23 | pivot = data[(left + right) / 2]; 24 | do 25 | { 26 | while ((comparer.Compare(data[i], pivot) < 0) && (i < right)) i++; 27 | while ((comparer.Compare(pivot, data[j]) < 0) && (j > left)) j--; 28 | if (i <= j) 29 | { 30 | temp = data[i]; 31 | data[i] = data[j]; 32 | data[j] = temp; 33 | i++; 34 | j--; 35 | } 36 | } while (i <= j); 37 | if (left < j) Quicksort(data, left, j, comparer); 38 | if (i < right) Quicksort(data, i, right, comparer); 39 | } 40 | 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # LineOS 3 | LineOS is an operating system made using the Cosmos OS construction kit. It's the first Cosmos OS to have support for the NTFS file system. 4 | 5 | NTFS implementation based on LordMike's [NtfsLib](https://github.com/LordMike/NtfsLib) licensed under MIT license. --------------------------------------------------------------------------------