/// <summary> /// Builds a 3-dimensional volume from the provided volume information. /// This method will parallelise voxel extraction per slice. /// </summary> /// <param name="volumeInformation">The volume information.</param> /// <param name="maxDegreeOfParallelism">The maximum degrees of parallelism when extracting voxel data from the DICOM datasets.</param> /// <returns>The 3-dimensional volume.</returns> /// <exception cref="ArgumentNullException">The provided volume information was null.</exception> /// <exception cref="InvalidOperationException">The decoded DICOM pixel data was not the expected length.</exception> public static Volume3D <short> BuildVolume( VolumeInformation volumeInformation, uint maxDegreeOfParallelism = 100) { volumeInformation = volumeInformation ?? throw new ArgumentNullException(nameof(volumeInformation)); // Allocate the array for reading the volume data. var result = new Volume3D <short>( (int)volumeInformation.Width, (int)volumeInformation.Height, (int)volumeInformation.Depth, volumeInformation.VoxelWidthInMillimeters, volumeInformation.VoxelHeightInMillimeters, volumeInformation.VoxelDepthInMillimeters, volumeInformation.Origin, volumeInformation.Direction); Parallel.For( 0, volumeInformation.Depth, new ParallelOptions() { MaxDegreeOfParallelism = (int)maxDegreeOfParallelism }, i => WriteSlice(result, volumeInformation.GetSliceInformation((int)i), (uint)i)); return(result); }
/// <summary> /// Gets volume information for the given volume root path. /// </summary> public static VolumeInformation GetVolumeInformation(string rootPath) { rootPath = Paths.AddTrailingSeparator(rootPath); using (var volumeName = new StringBuffer(initialCharCapacity: Paths.MaxPath + 1)) using (var fileSystemName = new StringBuffer(initialCharCapacity: Paths.MaxPath + 1)) { // Documentation claims that removable (floppy/optical) drives will prompt for media when calling this API and say to // set the error mode to prevent it. I can't replicate this behavior or find any documentation on when it might have // changed. I'm guessing this changed in Windows 7 when they added support for setting the thread's error mode (as // opposed to the entire process). uint serialNumber, maxComponentLength; FileSystemFeature flags; if (!Direct.GetVolumeInformationW(rootPath, volumeName, volumeName.CharCapacity, out serialNumber, out maxComponentLength, out flags, fileSystemName, fileSystemName.CharCapacity)) { throw ErrorHelper.GetIoExceptionForLastError(rootPath); } volumeName.SetLengthToFirstNull(); fileSystemName.SetLengthToFirstNull(); VolumeInformation info = new VolumeInformation { RootPathName = rootPath, VolumeName = volumeName.ToString(), VolumeSerialNumber = serialNumber, MaximumComponentLength = maxComponentLength, FileSystemFlags = flags, FileSystemName = fileSystemName.ToString() }; return(info); } }
internal static VolumeInformation GetVolumeInformation(string rootPath) { rootPath = Paths.AddTrailingSeparator(rootPath); using (var volumeName = new StringBuffer(initialMinCapacity: Paths.MaxPath + 1)) using (var fileSystemName = new StringBuffer(initialMinCapacity: Paths.MaxPath + 1)) { uint serialNumber, maxComponentLength; FileSystemFeature flags; if (!Private.GetVolumeInformationW(rootPath, volumeName, (uint)volumeName.CharCapacity, out serialNumber, out maxComponentLength, out flags, fileSystemName, (uint)fileSystemName.CharCapacity)) { int lastError = Marshal.GetLastWin32Error(); throw GetIoExceptionForError(lastError, rootPath); } volumeName.SetLengthToFirstNull(); fileSystemName.SetLengthToFirstNull(); VolumeInformation info = new VolumeInformation { RootPathName = rootPath, VolumeName = volumeName.ToString(), VolumeSerialNumber = serialNumber, MaximumComponentLength = maxComponentLength, FileSystemFlags = flags, FileSystemName = fileSystemName.ToString() }; return(info); } }
protected override ExitCode ExecuteFileTask() { VolumeInformation info = ExtendedFileService.GetVolumeInformation(Arguments.Target); Table table = Table.Create(new ColumnFormat(1, ContentVisibility.ShowAll, Justification.Right), new ColumnFormat(1)); table.HasHeader = false; table.AddRow("Volume Name", info.VolumeName); table.AddRow("Serial Number", info.VolumeSerialNumber.ToString()); table.AddRow("Max Component Length", info.MaximumComponentLength.ToString()); table.AddRow("File System Name", info.FileSystemName.ToString()); foreach (var value in Enum.GetValues(typeof(FileSystemFeature))) { FileSystemFeature feature = (FileSystemFeature)value; if ((feature & info.FileSystemFlags) == feature) { table.AddRow(feature.ToString(), "true"); } else { table.AddRow(feature.ToString(), "false"); } } this.Loggers[LoggerType.Result].Write(table); return(ExitCode.Success); }
public void TestVolumeInformationCreateTest() { ushort highBit = 16; var expectedDimX = 54; var expectedDimY = 64; var expectedDimZ = 50; var expectedSpacingX = 3; var expectedSpacingY = 5; var expectedSpacingZ = 4; var expectedOrigin = new Point3D(1, 2, 3); var dicomDatasets = CreateValidDicomDatasetVolume( expectedDimX, expectedDimY, expectedDimZ, expectedSpacingX, expectedSpacingY, expectedSpacingZ, expectedOrigin, DicomUID.CTImageStorage, highBit); var volumeInformation = VolumeInformation.Create(dicomDatasets); Assert.AreEqual(expectedDimX, volumeInformation.Width); Assert.AreEqual(expectedDimY, volumeInformation.Height); Assert.AreEqual(expectedDimZ, volumeInformation.Depth); Assert.AreEqual(expectedSpacingX, volumeInformation.VoxelWidthInMillimeters); Assert.AreEqual(expectedSpacingY, volumeInformation.VoxelHeightInMillimeters); Assert.AreEqual(expectedSpacingZ, volumeInformation.VoxelDepthInMillimeters); Assert.AreEqual(0, volumeInformation.RescaleIntercept); Assert.AreEqual(1, volumeInformation.RescaleSlope); Assert.AreEqual(highBit, volumeInformation.HighBit); Assert.AreEqual(true, volumeInformation.SignedPixelRepresentation); Assert.AreEqual(expectedOrigin.X, volumeInformation.Origin.X); Assert.AreEqual(expectedOrigin.Y, volumeInformation.Origin.Y); Assert.AreEqual(expectedOrigin.Z, volumeInformation.Origin.Z); Assert.AreEqual(Matrix3.CreateIdentity(), volumeInformation.Direction); for (var i = 0; i < dicomDatasets.Length; i++) { Assert.AreEqual(i * expectedSpacingZ + expectedOrigin.Z, volumeInformation.GetSliceInformation(i).SlicePosition); } // Exception testing. dicomDatasets[dicomDatasets.Length - 1].AddOrUpdate(new DicomDecimalString(DicomTag.PixelSpacing, new decimal[] { 0, 0 })); Assert.Throws <ArgumentException>(() => VolumeInformation.Create(dicomDatasets.Take(1))); var sliceInformation = new SliceInformation[1]; Assert.Throws <ArgumentException>(() => VolumeInformation.Create(sliceInformation)); sliceInformation = null; Assert.Throws <ArgumentNullException>(() => VolumeInformation.Create(sliceInformation)); dicomDatasets = null; Assert.Throws <ArgumentNullException>(() => VolumeInformation.Create(dicomDatasets)); }
static void Main(string[] args) { if (0 == args.Length) { string executableName = AssemblyHelper.ExecutableName; string applicationName = Path.GetFileNameWithoutExtension(executableName); Console.WriteLine("{0} [UNC-path|Drive]...", applicationName); Console.WriteLine(@"Example: {0} C: \\{1}\Users", applicationName, Environment.MachineName); } else { string header = string.Empty; StringBuilder result = new StringBuilder(); foreach (string arg in args) { try { DiskFreeSpaceEx diskFreeSpaceEx = DiskInfo.GetDiskFreeSpaceEx(arg); VolumeInformation volumeInformation = DiskInfo.GetVolumeInformation(arg); var values = new { diskFreeSpaceEx.DirectoryName, diskFreeSpaceEx.FreeBytesAvailable, diskFreeSpaceEx.TotalNumberOfBytes, diskFreeSpaceEx.TotalNumberOfFreeBytes, volumeInformation.VolumeSerialNumber, volumeInformation.MaximumComponentLength, volumeInformation.FileSystemFlags, volumeInformation.FileSystemName, volumeInformation.VolumeName, }; IList <string> valueStrings = Reflector.GetValueStrings(values); const string separator = ";"; string line = string.Join(separator, valueStrings); result.AppendLine(line); IList <string> names = Reflector.GetNames(values); header = string.Join(separator, names); } catch (Exception ex) { Console.WriteLine("Exception processing '{0}': {1}", arg, ex); } } if (result.Length > 0) { Console.WriteLine(header); Console.Write(result); } } }
/// <summary> /// Checks the slice spacing conformance based on the acceptance test. /// </summary> /// <param name="volumeInformation">The volume information.</param> /// <param name="acceptanceTest">The acceptance test.</param> /// <exception cref="ArgumentNullException">The volume information or acceptance test was null.</exception> /// <exception cref="ArgumentException">The acceptance test did not pass for a slice.</exception> private static void CheckSliceSpacingConformance(VolumeInformation volumeInformation, IVolumeGeometricAcceptanceTest acceptanceTest) { for (var i = 1; i < volumeInformation.Depth; i++) { var spacing = volumeInformation.GetSliceInformation(i).SlicePosition - volumeInformation.GetSliceInformation(i - 1).SlicePosition; if (!acceptanceTest.AcceptSliceSpacingError(volumeInformation.SopClass, spacing, volumeInformation.VoxelDepthInMillimeters)) { throw new ArgumentException($"The spacing between slice {i - 1} and {i} was inconsistent."); } } }
/// <summary> /// /// </summary> protected override void ProcessRecord() { switch (ParameterSetName) { case "ByVolume": WriteObject(VolumeInformation.Get(volume)); break; case "ByPath": WriteObject(VolumeInformation.GetByPath(path)); break; } }
public void SystemDirTest() { VolumeInformation container = new VolumeInformation(new DirectoryInfo(Environment.SystemDirectory)); Assert.Equal(_isCaseSensitive, container.IsCaseSensitive); Assert.Equal(_fileSystemFlags, container.Flags); Assert.Equal(_fileSystemName, container.FileSystemName); Assert.Equal(_maximumComponentLength, container.MaxNameLength); Assert.Equal(_rootDirectory.FullName, container.RootPathName); Assert.Equal(_rootDirectory.Name, container.Name); Assert.Equal(_volumeName, container.VolumeName); Assert.Equal(_volumeSerialNumber, container.SerialNumber); }
/// <summary> /// Attempt to construct a 3-dimensional volume instance from the provided set of DICOM series files. /// </summary> /// <param name="dicomDatasets">The collection of DICOM datasets.</param> /// <param name="acceptanceTest">An implmentation of IVolumeGeometricAcceptanceTest expressing the geometric tollerances required by your application</param> /// <param name="supportLossyCodecs">true if it is appropriate for your application to support lossy pixel encodings</param> /// <returns>The created 3-dimensional volume.</returns> /// <exception cref="ArgumentNullException">The DICOM datasets or acceptance test is null.</exception> /// <exception cref="ArgumentException">A volume could not be formed from the provided DICOM series datasets.</exception> public static Volume3D <short> BuildVolume( IEnumerable <DicomDataset> dicomDatasets, IVolumeGeometricAcceptanceTest acceptanceTest, bool supportLossyCodecs) { dicomDatasets = dicomDatasets ?? throw new ArgumentNullException(nameof(dicomDatasets)); acceptanceTest = acceptanceTest ?? throw new ArgumentNullException(nameof(acceptanceTest)); // 1. Construct the volume information: this requires a minimum set of DICOM tags in each dataset. var volumeInformation = VolumeInformation.Create(dicomDatasets); // 2. Now validate the volume based on the acceptance tests (will throw argument exception on failure). DicomSeriesInformationValidator.ValidateVolumeInformation(volumeInformation, acceptanceTest, supportLossyCodecs ? null : SupportedTransferSyntaxes); // 3. Now validated, lets extract the pixels as a short array. return(DicomSeriesImageReader.BuildVolume(volumeInformation)); }
public VolumeInformationTest(ITestOutputHelper outputHelper) { StringBuilder fileSystemNameBuffer = new StringBuilder(VolumeInformation.InteropStringCapacity); StringBuilder volumeNameBuffer = new StringBuilder(VolumeInformation.InteropStringCapacity); _rootDirectory = (new DirectoryInfo(Environment.SystemDirectory)).Root; if (!VolumeInformation.GetVolumeInformation(_rootDirectory.FullName, volumeNameBuffer, VolumeInformation.InteropStringCapacity, out uint volumeSerialNumber, out uint maximumComponentLength, out FileSystemFeature fileSystemFlags, fileSystemNameBuffer, VolumeInformation.InteropStringCapacity)) { Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error()); } _fileSystemFlags = fileSystemFlags; _isCaseSensitive = fileSystemFlags.HasFlag(FileSystemFeature.CaseSensitiveSearch); _volumeSerialNumber = volumeSerialNumber; _maximumComponentLength = maximumComponentLength; _fileSystemName = fileSystemNameBuffer.ToString(); _volumeName = volumeNameBuffer.ToString(); }
public void TestVolumeInformationValidation() { var dicomDatasets = CreateValidDicomDatasetVolume(5, 5, 5, 1, 1, 3, new Point3D(), DicomUID.CTImageStorage, 16); var acceptanceTest = new ModerateGeometricAcceptanceTest("Blah1", "Blah2"); // Valid DICOM slice. DicomSeriesInformationValidator.ValidateVolumeInformation( VolumeInformation.Create(dicomDatasets), acceptanceTest, new[] { dicomDatasets[0].InternalTransferSyntax }); // Inconsistent slice information. var dicomDatasets2 = CreateValidDicomDatasetVolume(5, 5, 5, 1, 1, 3, new Point3D(), DicomUID.CTImageStorage, 16); dicomDatasets2[dicomDatasets2.Length - 2].AddOrUpdate(new DicomUnsignedShort(DicomTag.Rows, 234)); var exception = Assert.Throws <ArgumentException>(() => DicomSeriesInformationValidator.ValidateVolumeInformation( VolumeInformation.Create(dicomDatasets2), acceptanceTest)); Assert.IsTrue(exception.Message.Contains("Slice at position '9' has an inconsistent height. Expected: '5', Actual: '234'.")); // Invalid supported transfer syntax Assert.Throws <ArgumentException>(() => DicomSeriesInformationValidator.ValidateVolumeInformation( VolumeInformation.Create(dicomDatasets), acceptanceTest, new[] { DicomTransferSyntax.DeflatedExplicitVRLittleEndian })); // Failing acceptance test Assert.Throws <ArgumentException>(() => DicomSeriesInformationValidator.ValidateVolumeInformation( VolumeInformation.Create(dicomDatasets), new FailingAcceptanceTest())); // Exception testing Assert.Throws <ArgumentNullException>(() => DicomSeriesInformationValidator.ValidateVolumeInformation( VolumeInformation.Create(dicomDatasets), null)); Assert.Throws <ArgumentNullException>(() => DicomSeriesInformationValidator.ValidateVolumeInformation( null, acceptanceTest)); }
/// <summary> /// Validates the provided volume information in accordance with the provided volume geometric acceptance test and /// that every slice in the volume is valid. /// This will check: /// 1. Validates each slice using the validate slice information method (and will use the supported transfer syntaxes if provided). /// 2. Grid conformance of the volume. /// 3. Slice spacing conformance for each slice. /// 4. Executes the propose method of the acceptance test. /// </summary> /// <param name="volumeInformation">The volume information.</param> /// <param name="volumeGeometricAcceptanceTest">The volume geometric acceptance test.</param> /// <param name="supportedTransferSyntaxes">The supported transfer syntaxes or null if we do not want to check against this.</param> /// <exception cref="ArgumentNullException">The volume information or acceptance test is null.</exception> /// <exception cref="ArgumentException">An acceptance test did not pass.</exception> public static void ValidateVolumeInformation( VolumeInformation volumeInformation, IVolumeGeometricAcceptanceTest volumeGeometricAcceptanceTest, IReadOnlyCollection <DicomTransferSyntax> supportedTransferSyntaxes = null) { volumeInformation = volumeInformation ?? throw new ArgumentNullException(nameof(volumeInformation)); volumeGeometricAcceptanceTest = volumeGeometricAcceptanceTest ?? throw new ArgumentNullException(nameof(volumeGeometricAcceptanceTest)); // 1. Validate each slice. for (var i = 0; i < volumeInformation.Depth; i++) { // Validate the DICOM tags of each slice. ValidateSliceInformation(volumeInformation.GetSliceInformation(i), supportedTransferSyntaxes); if (i > 0) { // Validate the slice information is consistent across slices using the first slice as a reference. ValidateSliceInformation(volumeInformation.GetSliceInformation(i), volumeInformation.GetSliceInformation(0)); } } // 2. + 3. Check the slice and grid conformance of the volume information. CheckGridConformance(volumeInformation, volumeGeometricAcceptanceTest); CheckSliceSpacingConformance(volumeInformation, volumeGeometricAcceptanceTest); // 4. Run acceptance testing. var acceptanceErrorMessage = string.Empty; if (!volumeGeometricAcceptanceTest.Propose( volumeInformation.SopClass, volumeInformation.Origin, volumeInformation.Direction, new Point3D(volumeInformation.VoxelWidthInMillimeters, volumeInformation.VoxelHeightInMillimeters, volumeInformation.Depth), out acceptanceErrorMessage)) { throw new ArgumentException(acceptanceErrorMessage, nameof(volumeInformation)); } }
/// <summary> /// Checks the grid conformance of the provided volume information based on the provided geometric acceptance test. /// </summary> /// <param name="volumeInformation">The volume information.</param> /// <param name="acceptanceTest">The acceptance test.</param> /// <exception cref="ArgumentNullException">The volume information or acceptance test was null.</exception> /// <exception cref="ArgumentException">The series did not conform to a regular grid.</exception> private static void CheckGridConformance(VolumeInformation volumeInformation, IVolumeGeometricAcceptanceTest acceptanceTest) { volumeInformation = volumeInformation ?? throw new ArgumentNullException(nameof(volumeInformation)); acceptanceTest = acceptanceTest ?? throw new ArgumentNullException(nameof(acceptanceTest)); var scales = Matrix3.Diag( volumeInformation.VoxelWidthInMillimeters, volumeInformation.VoxelHeightInMillimeters, volumeInformation.VoxelDepthInMillimeters); if (volumeInformation.Depth != volumeInformation.Depth) { throw new ArgumentException("Mismatch between depth and number of slices.", nameof(volumeInformation)); } for (int z = 0; z < volumeInformation.Depth; z++) { var sliceInformation = volumeInformation.GetSliceInformation(z); var sliceScales = Matrix3.Diag(sliceInformation.VoxelWidthInMillimeters, sliceInformation.VoxelHeightInMillimeters, 0); var sliceOrientation = Matrix3.FromColumns(sliceInformation.Direction.Column(0), sliceInformation.Direction.Column(1), new Point3D(0, 0, 0)); // Check corners of each slice only for (uint y = 0; y < sliceInformation.Height; y += sliceInformation.Height - 1) { for (uint x = 0; x < sliceInformation.Width; x += sliceInformation.Width - 1) { var point = new Point3D(x, y, z); var patientCoordViaSliceFrame = sliceOrientation * sliceScales * point + sliceInformation.Origin; var patientCoordViaGridFrame = volumeInformation.Direction * scales * point + volumeInformation.Origin; if (!acceptanceTest.AcceptPositionError(volumeInformation.SopClass, patientCoordViaSliceFrame, patientCoordViaGridFrame)) { throw new ArgumentException("The series did not conform to a regular grid.", nameof(volumeInformation)); } } } } }
/// <summary> /// Run a test for the block manipulation methods. /// </summary> /// <param name="hVolume">The current volume pointer.</param> /// <param name="nOpType">The current operation type.</param> /// <returns></returns> public static void RunTest(IntPtr hVolume, XTensionActionSource nOpType) { HelperMethods.OutputMessage("VOLUME INTERACTION TESTING MODULE" , OutputMessageOptions.Level1 | OutputMessageOptions.Header); // GetSize() test. HelperMethods.OutputMessage("GetSize() Test" , OutputMessageOptions.Level2 | OutputMessageOptions.Header); var itemPhysicalSize = HelperMethods.GetSize(hVolume , ItemSizeType.PhysicalSize); HelperMethods.OutputMessage("Volume Physical Size: " + itemPhysicalSize, OutputMessageOptions.Level3); var itemLogicalSize = HelperMethods.GetSize(hVolume , ItemSizeType.LogicalSize); HelperMethods.OutputMessage("Volume Logical Size: " + itemLogicalSize, OutputMessageOptions.Level3); var itemValidDateLength = HelperMethods.GetSize(hVolume , ItemSizeType.ValidDataLength); HelperMethods.OutputMessage("Volume Valid Data Length: " + itemValidDateLength, OutputMessageOptions.Level3); HelperMethods.OutputMessage(""); // GetVolumeName() test. HelperMethods.OutputMessage("GetVolumeName() Test" , OutputMessageOptions.Level2 | OutputMessageOptions.Header); string type1VolumeName = HelperMethods.GetVolumeName(hVolume , VolumeNameType.Type1); HelperMethods.OutputMessage("Type 1 Volume Name: " + type1VolumeName , OutputMessageOptions.Level3); string type2VolumeName = HelperMethods.GetVolumeName(hVolume , VolumeNameType.Type2); HelperMethods.OutputMessage("Type 2 Volume Name: " + type2VolumeName , OutputMessageOptions.Level3); string type3VolumeName = HelperMethods.GetVolumeName(hVolume , VolumeNameType.Type3); HelperMethods.OutputMessage("Type 3 Volume Name: " + type3VolumeName , OutputMessageOptions.Level3); HelperMethods.OutputMessage(""); // GetVolumeInformation() test. HelperMethods.OutputMessage("GetVolumeInformation() Test" , OutputMessageOptions.Level2 | OutputMessageOptions.Header); VolumeInformation volumeInformation = HelperMethods.GetVolumeInformation(hVolume); HelperMethods.OutputMessage("File System: " + volumeInformation.FileSystem, OutputMessageOptions.Level3); HelperMethods.OutputMessage("Bytes/Sector: " + volumeInformation.BytesPerSector, OutputMessageOptions.Level3); HelperMethods.OutputMessage("Sectors/Cluster: " + volumeInformation.SectorsPerCluster, OutputMessageOptions.Level3); HelperMethods.OutputMessage("Cluster Count: " + volumeInformation.ClusterCount, OutputMessageOptions.Level3); HelperMethods.OutputMessage("First Cluster Sector Number: " + volumeInformation.FirstClusterSectorNumber, OutputMessageOptions.Level3); HelperMethods.OutputMessage(""); }
static void Main(string[] args) { var volumeInfo = VolumeInformation.GetVolumeInformation("c"); }
public FileRecord(byte[] rawBytes, int offset) { Offset = offset; var sig = BitConverter.ToInt32(rawBytes, 0); if ((sig != _fileSig) && (sig != _baadSig) && (sig != 0x0)) { Logger.Fatal($"Invalid signature! 0x{sig:X}"); return; //throw new Exception("Invalid signature!"); } if (sig == _baadSig) { Logger.Warn($"Bad signature at offset 0x{offset:X}"); return; } Attributes = new List <Attribute>(); FixupOffset = BitConverter.ToInt16(rawBytes, 2); FixupEntryCount = BitConverter.ToInt16(rawBytes, 4); LogSequenceNumber = BitConverter.ToInt64(rawBytes, 0x8); SequenceNumber = BitConverter.ToInt16(rawBytes, 0x10); ReferenceCount = BitConverter.ToInt16(rawBytes, 0x12); FirstAttributeOffset = BitConverter.ToInt16(rawBytes, 0x14); EntryFlags = (EntryFlag)BitConverter.ToInt16(rawBytes, 0x16); Logger.Trace($"Entry flags: {EntryFlags}"); ActualRecordSize = BitConverter.ToInt32(rawBytes, 0x18); AllocatedRecordSize = BitConverter.ToInt32(rawBytes, 0x1c); var entryBytes = new byte[8]; Buffer.BlockCopy(rawBytes, 0x20, entryBytes, 0, 8); MFTRecordToBaseRecord = new MftEntryInfo(entryBytes); FirstAvailableAttribueId = BitConverter.ToInt16(rawBytes, 0x28); EntryNumber = BitConverter.ToInt32(rawBytes, 0x2c); var fixupExpectedBytes = new byte[2]; var fixupActual1 = new byte[2]; var fixupActual2 = new byte[2]; Buffer.BlockCopy(rawBytes, 0x30, fixupExpectedBytes, 0, 2); Buffer.BlockCopy(rawBytes, 0x32, fixupActual1, 0, 2); Buffer.BlockCopy(rawBytes, 0x34, fixupActual2, 0, 2); //verify this record looks ok based on fixup bytes //0x1FE and 0x3fe var expectedFixupVal = BitConverter.ToInt16(fixupExpectedBytes, 0); var x1FeValue = BitConverter.ToInt16(rawBytes, 0x1FE); var x3FeValue = BitConverter.ToInt16(rawBytes, 0x3FE); if ((x1FeValue != expectedFixupVal) && ((EntryFlags & EntryFlag.FileRecordSegmentInUse) == EntryFlag.FileRecordSegmentInUse)) { Logger.Warn( $"FILE record at offset 0x{offset:X}! Fixup values do not match at 0x1FE. Expected: {expectedFixupVal}, actual: {x1FeValue}, EntryFlags: {EntryFlags}"); } if ((x3FeValue != expectedFixupVal) && ((EntryFlags & EntryFlag.FileRecordSegmentInUse) == EntryFlag.FileRecordSegmentInUse)) { Logger.Warn( $"FILE record at offset 0x{offset:X}! Fixup values do not match at 0x3FE. Expected: {expectedFixupVal}, actual: {x3FeValue}, EntryFlags: {EntryFlags}"); } //header is done, replace fixup bytes with actual bytes //0x1fe and 0x3fe should contain fixup bytes Buffer.BlockCopy(fixupActual1, 0, rawBytes, 0x1fe, 2); Buffer.BlockCopy(fixupActual2, 0, rawBytes, 0x3fe, 2); //start attribute processing at FirstAttributeOffset var index = (int)FirstAttributeOffset; while (index < ActualRecordSize) { var attrType = BitConverter.ToInt32(rawBytes, index); var attrSize = BitConverter.ToInt32(rawBytes, index + 4); // Logger.Trace( // $"ActualRecordSize: {ActualRecordSize} attrType: 0x{attrType:X}, size: {attrSize}, index: {index}, offset: 0x{offset:x}, i+o: 0x{index + offset:X}"); if ((attrSize == 0) || (attrType == -1)) { index += 8; //skip -1 type and 0 size if (EntryFlags == 0) //this is a free record { break; } continue; } var rawAttr = new byte[attrSize]; Buffer.BlockCopy(rawBytes, index, rawAttr, 0, attrSize); switch ((AttributeType)attrType) { case AttributeType.StandardInformation: var si = new StandardInfo(rawAttr); Attributes.Add(si); SILastAccessedOn = si.LastAccessedOn; SICreatedOn = si.CreatedOn; SIRecordModifiedOn = si.RecordModifiedOn; SIContentModifiedOn = si.ContentModifiedOn; break; case AttributeType.FileName: var fi = new FileName(rawAttr); Attributes.Add(fi); if ((fi.FileInfo.NameType & NameTypes.Windows) == NameTypes.Windows) { FName = fi.FileInfo.FileName; } //if (fi.FileInfo.LastAccessedOn.UtcDateTime != SILastAccessedOn.UtcDateTime) //{ FNLastAccessedOn = fi.FileInfo.LastAccessedOn; //} //if (fi.FileInfo.CreatedOn.UtcDateTime != SICreatedOn.UtcDateTime) //{ FNCreatedOn = fi.FileInfo.CreatedOn; //} //if (fi.FileInfo.RecordModifiedOn.UtcDateTime != SIRecordModifiedOn.UtcDateTime) //{ FNRecordModifiedOn = fi.FileInfo.RecordModifiedOn; //} //if (fi.FileInfo.ContentModifiedOn.UtcDateTime != SIContentModifiedOn.UtcDateTime) //{ FNContentModifiedOn = fi.FileInfo.ContentModifiedOn; //} break; case AttributeType.Data: var data = new Data(rawAttr); Attributes.Add(data); break; case AttributeType.IndexAllocation: var ia = new IndexAllocation(rawAttr); Attributes.Add(ia); break; case AttributeType.IndexRoot: var ir = new IndexRoot(rawAttr); Attributes.Add(ir); break; case AttributeType.Bitmap: var bm = new Bitmap(rawAttr); Attributes.Add(bm); break; case AttributeType.VolumeVersionObjectId: var oi = new ObjectId(rawAttr); Attributes.Add(oi); break; case AttributeType.SecurityDescriptor: var sd = new SecurityDescriptor(rawAttr); Attributes.Add(sd); break; case AttributeType.VolumeName: var vn = new VolumeName(rawAttr); Attributes.Add(vn); break; case AttributeType.VolumeInformation: var vi = new VolumeInformation(rawAttr); Attributes.Add(vi); break; case AttributeType.LoggedUtilityStream: var lus = new LoggedUtilityStream(rawAttr); Attributes.Add(lus); break; case AttributeType.ReparsePoint: var rp = new ReparsePoint(rawAttr); Attributes.Add(rp); break; case AttributeType.AttributeList: var al = new AttributeList(rawAttr); Attributes.Add(al); break; case AttributeType.Ea: //TODO Finish this var ea = new ExtendedAttribute(rawAttr); Attributes.Add(ea); break; case AttributeType.EaInformation: var eai = new ExtendedAttributeInformation(rawAttr); Attributes.Add(eai); break; default: Logger.Warn($"Unhandled attribute type! Add me: {(AttributeType) attrType}"); throw new Exception($"Add me: {(AttributeType) attrType}"); break; } index += attrSize; } SlackStartOffset = index; //rest is slack. handle here? Logger.Trace($"Slack starts at {index} i+o: 0x{index + offset:X}"); }
public extern static UInt32 GetVolumeInformation(IntPtr inVolumeRef, out VolumeInformation outVolumeInfo);
public FileRecord(byte[] rawBytes, int offset) { Offset = offset; var sig = BitConverter.ToInt32(rawBytes, 0); switch (sig) { case FileSig: break; case BaadSig: _logger.Debug($"Bad signature at offset 0x{offset:X}"); IsBad = true; return; default: //not initialized _logger.Debug($"Uninitialized entry (no signature) at offset 0x{offset:X}"); IsUninitialized = true; return; } _logger.Debug($"Processing FILE record at offset 0x{offset:X}"); Attributes = new List <Attribute>(); FixupOffset = BitConverter.ToInt16(rawBytes, 0x4); FixupEntryCount = BitConverter.ToInt16(rawBytes, 0x6); //to build fixup info, take FixupEntryCount x 2 bytes as each are 2 bytes long var fixupTotalLength = FixupEntryCount * 2; var fixupBuffer = new byte[fixupTotalLength]; Buffer.BlockCopy(rawBytes, FixupOffset, fixupBuffer, 0, fixupTotalLength); //pull this early so we can check if its free in our fix up value messages EntryFlags = (EntryFlag)BitConverter.ToInt16(rawBytes, 0x16); FixupData = new FixupData(fixupBuffer); FixupOk = true; //fixup verification var counter = 512; foreach (var bytese in FixupData.FixupActual) { //adjust the offset to where we need to check var fixupOffset = counter - 2; var expected = BitConverter.ToInt16(rawBytes, fixupOffset); if (expected != FixupData.FixupExpected && EntryFlags != 0x0) { FixupOk = false; _logger.Warn( $"Offset: 0x{Offset:X} Entry/seq: 0x{EntryNumber:X}/0x{SequenceNumber:X} Fixup values do not match at 0x{fixupOffset:X}. Expected: 0x{FixupData.FixupExpected:X2}, actual: 0x{expected:X2}"); } //replace fixup expected with actual bytes. bytese has actual replacement values in it. Buffer.BlockCopy(bytese, 0, rawBytes, fixupOffset, 2); counter += 512; } LogSequenceNumber = BitConverter.ToInt64(rawBytes, 0x8); SequenceNumber = BitConverter.ToUInt16(rawBytes, 0x10); ReferenceCount = BitConverter.ToInt16(rawBytes, 0x12); FirstAttributeOffset = BitConverter.ToInt16(rawBytes, 0x14); ActualRecordSize = BitConverter.ToInt32(rawBytes, 0x18); AllocatedRecordSize = BitConverter.ToInt32(rawBytes, 0x1c); var entryBytes = new byte[8]; Buffer.BlockCopy(rawBytes, 0x20, entryBytes, 0, 8); MftRecordToBaseRecord = new MftEntryInfo(entryBytes); FirstAvailablAttribueId = BitConverter.ToInt16(rawBytes, 0x28); EntryNumber = BitConverter.ToUInt32(rawBytes, 0x2c); //start attribute processing at FirstAttributeOffset var index = (int)FirstAttributeOffset; while (index < ActualRecordSize) { var attrType = (AttributeType)BitConverter.ToInt32(rawBytes, index); var attrSize = BitConverter.ToInt32(rawBytes, index + 4); if (attrSize == 0 || attrType == AttributeType.EndOfAttributes) { index += 8; //skip -1 type and 0 size if (index != ActualRecordSize) { _logger.Warn($"Slack space found in entry/seq: 0x{EntryNumber:X}/0x{SequenceNumber:X}"); } //TODO process slack here? break; } _logger.Debug( $"Found Attribute Type {attrType.ToString()} at absolute offset: 0x{index + offset:X}"); _logger.Trace( $"ActualRecordSize: 0x{ActualRecordSize:X}, size: 0x{attrSize:X}, index: 0x{index:X}"); var rawAttr = new byte[attrSize]; Buffer.BlockCopy(rawBytes, index, rawAttr, 0, attrSize); switch (attrType) { case AttributeType.StandardInformation: var si = new StandardInfo(rawAttr); Attributes.Add(si); break; case AttributeType.FileName: var fi = new FileName(rawAttr); Attributes.Add(fi); break; case AttributeType.Data: var d = new Data(rawAttr); Attributes.Add(d); break; case AttributeType.IndexAllocation: var ia = new IndexAllocation(rawAttr); Attributes.Add(ia); break; case AttributeType.IndexRoot: var ir = new IndexRoot(rawAttr); Attributes.Add(ir); break; case AttributeType.Bitmap: var bm = new Bitmap(rawAttr); Attributes.Add(bm); break; case AttributeType.VolumeVersionObjectId: var oi = new ObjectId_(rawAttr); Attributes.Add(oi); break; case AttributeType.SecurityDescriptor: var sd = new SecurityDescriptor(rawAttr); Attributes.Add(sd); break; case AttributeType.VolumeName: var vn = new VolumeName(rawAttr); Attributes.Add(vn); break; case AttributeType.VolumeInformation: var vi = new VolumeInformation(rawAttr); Attributes.Add(vi); break; case AttributeType.LoggedUtilityStream: var lus = new LoggedUtilityStream(rawAttr); Attributes.Add(lus); break; case AttributeType.ReparsePoint: try { var rp = new ReparsePoint(rawAttr); Attributes.Add(rp); } catch (Exception) { var l = LogManager.GetLogger("ReparsePoint"); l.Error( $"There was an error parsing a ReparsePoint in FILE record at offset 0x{Offset:X}. Please extract via --dd and --do and send to [email protected]"); } break; case AttributeType.AttributeList: var al = new AttributeList(rawAttr); Attributes.Add(al); break; case AttributeType.Ea: var ea = new ExtendedAttribute(rawAttr); Attributes.Add(ea); break; case AttributeType.EaInformation: var eai = new ExtendedAttributeInformation(rawAttr); Attributes.Add(eai); break; default: throw new Exception($"Add me: {attrType} (0x{attrType:X})"); } index += attrSize; } //rest is slack. handle here? _logger.Trace($"Slack starts at 0x{index:X} Absolute offset: 0x{index + offset:X}"); }