/// <summary> /// Exports the current header to a 1024 byte array /// </summary> /// <remarks> /// This will not validate the disk or update the header. /// Use <see cref="Validate"/> and <see cref="ComputeChecksum"/> before exporting /// </remarks> /// <returns>Raw header data</returns> public byte[] Export() { using (var MS = new MemoryStream()) { using (var BW = new BinaryWriter(MS)) { BW.Write(Encoding.Default.GetBytes(Cookie)); BW.Write(Tools.ToNetwork(DataOffset)); BW.Write(Tools.ToNetwork(TableOffset)); BW.Write(Tools.ToNetwork((ushort)HeaderVersion.Major)); BW.Write(Tools.ToNetwork((ushort)HeaderVersion.Minor)); BW.Write(Tools.ToNetwork(MaxTableEntries)); BW.Write(Tools.ToNetwork(BlockSize)); BW.Write(Tools.ToNetwork(Checksum)); BW.Write(ParentUniqueId.ToByteArray()); BW.Write(Tools.ToNetwork((uint)VHD.ToDiskTimestamp(ParentTimeStamp))); BW.Write(Reserved1); BW.Write(Encoding.Unicode.GetBytes(ParentUnicodeName)); foreach (var E in ParentLocatorEntries) { BW.Write(E.Export()); } BW.Write(Reserved2); BW.Flush(); return(MS.ToArray()); } } }
/// <summary> /// Reads a stream into a VHD header /// </summary> /// <param name="S">Stream</param> private void FromStream(Stream S) { Encoding E = Encoding.Default; //The comments indicate the offset from the header start using (var BR = new BinaryReader(S, E, true)) { //0 Cookie = E.GetString(BR.ReadBytes(8)); //8 Features = (VhdFeatures)Tools.ToNetwork(BR.ReadUInt32()); //12 FileFormatVersion = new Version(Tools.ToNetwork(BR.ReadUInt16()), Tools.ToNetwork(BR.ReadUInt16())); //16 DataOffset = Tools.ToNetwork(BR.ReadUInt64()); //24 TimeStamp = VHD.FromDiskTimestamp(Tools.ToNetwork(BR.ReadInt32())); //28 CreatorApplication = E.GetString(BR.ReadBytes(4)); //32 CreatorVersion = new Version(Tools.ToNetwork(BR.ReadUInt16()), Tools.ToNetwork(BR.ReadUInt16())); //36 CreatorHostOS = E.GetString(BR.ReadBytes(4)); //40 OriginalSize = Tools.ToNetwork(BR.ReadUInt64()); //48 CurrentSize = Tools.ToNetwork(BR.ReadUInt64()); //56 DiskGeometry = new CHS( Tools.ToNetwork(BR.ReadUInt16()), BR.ReadByte(), BR.ReadByte() ); //60 DiskType = (VhdType)Tools.ToNetwork(BR.ReadUInt32()); //64 Checksum = Tools.ToNetwork(BR.ReadInt32()); //68 DiskId = new Guid(BR.ReadBytes(16)); //84 SavedState = BR.ReadByte() == 1; //85 Reserved = BR.ReadBytes(RESERVED_FIELD_SIZE); //512 } }
/// <summary> /// Reads a stream into a VHD header /// </summary> /// <param name="S">Stream</param> private void FromStream(Stream S) { Encoding E = Encoding.Default; //The comments indicate the offset from the header start using (var BR = new BinaryReader(S, E, true)) { //0 Cookie = E.GetString(BR.ReadBytes(8)); //8 DataOffset = Tools.ToNetwork(BR.ReadUInt64()); //16 TableOffset = Tools.ToNetwork(BR.ReadUInt64()); //24 HeaderVersion = new Version(Tools.ToNetwork(BR.ReadUInt16()), Tools.ToNetwork(BR.ReadUInt16())); //28 MaxTableEntries = Tools.ToNetwork(BR.ReadUInt32()); //32 BlockSize = Tools.ToNetwork(BR.ReadUInt32()); //36 Checksum = Tools.ToNetwork(BR.ReadInt32()); //40 ParentUniqueId = new Guid(BR.ReadBytes(16)); //56 ParentTimeStamp = VHD.FromDiskTimestamp(Tools.ToNetwork(BR.ReadUInt32())); //60 Reserved1 = BR.ReadBytes(RESERVED1_FIELD_SIZE); //64 ParentUnicodeName = Encoding.Unicode.GetString(BR.ReadBytes(UNICODE_PARENT_LENGTH)); //576 ParentLocatorEntries = Enumerable .Range(0, LOCATOR_ENTRY_COUNT) .Select(m => new LocatorEntry(BR.ReadBytes(LocatorEntry.ENTRY_SIZE))) .ToArray(); //768 Reserved2 = BR.ReadBytes(RESERVED2_FIELD_SIZE); //1024 } }
/// <summary> /// Exports the current header to a 512 byte array /// </summary> /// <remarks> /// This will not validate the disk or update the header. /// Use <see cref="Validate"/> and <see cref="ComputeChecksum"/> before exporting /// </remarks> /// <returns>Raw header data</returns> public byte[] Export() { using (var MS = new MemoryStream()) { using (var BW = new BinaryWriter(MS)) { BW.Write(Encoding.Default.GetBytes(Cookie)); BW.Write(Tools.ToNetwork((uint)Features)); BW.Write(Tools.ToNetwork((ushort)FileFormatVersion.Major)); BW.Write(Tools.ToNetwork((ushort)FileFormatVersion.Minor)); BW.Write(Tools.ToNetwork(DataOffset)); BW.Write(Tools.ToNetwork((int)VHD.ToDiskTimestamp(TimeStamp))); BW.Write(Encoding.Default.GetBytes(CreatorApplication)); BW.Write(Tools.ToNetwork((ushort)CreatorVersion.Major)); BW.Write(Tools.ToNetwork((ushort)CreatorVersion.Minor)); BW.Write(Encoding.Default.GetBytes(CreatorHostOS)); BW.Write(Tools.ToNetwork(OriginalSize)); BW.Write(Tools.ToNetwork(CurrentSize)); BW.Write(Tools.ToNetwork((ushort)DiskGeometry.Cylinders)); BW.Write((byte)DiskGeometry.Heads); BW.Write((byte)DiskGeometry.SectorsPerTrack); BW.Write(Tools.ToNetwork((uint)DiskType)); BW.Write(Tools.ToNetwork(Checksum)); BW.Write(DiskId.ToByteArray()); BW.Write((byte)(SavedState ? 1 : 0)); BW.Write(Reserved); BW.Flush(); return(MS.ToArray()); } } }
/// <summary> /// Validates the header and throws an exception if it's invalid /// </summary> public void Validate() { var E = Encoding.Default; if (Cookie == null || E.GetBytes(Cookie).Length != 8) { throw new ValidationException(nameof(Cookie), "Must be an 8 byte (ANSI) string"); } if (DataOffset != DEFAULT_DATA_OFFSET) { throw new ValidationException(nameof(DataOffset), $"Is currently unused and must be set to {DEFAULT_DATA_OFFSET}"); } if (TableOffset < HEADER_LENGTH + Footer.HEADER_LENGTH) { throw new ValidationException(nameof(TableOffset), $"Can't be less than the footer size plus the dynamic header size"); } if (HeaderVersion == null) { throw new ValidationException(nameof(HeaderVersion), "Must be defined"); } if (HeaderVersion.Major < ushort.MinValue || HeaderVersion.Minor < ushort.MinValue || HeaderVersion.Major > ushort.MaxValue || HeaderVersion.Minor > ushort.MaxValue) { throw new ValidationException(nameof(HeaderVersion), $"Major and minor must range from {ushort.MinValue}-{ushort.MaxValue}"); } if (MaxTableEntries == 0) { throw new ValidationException(nameof(MaxTableEntries), "Can't be zero. It should be equal to the number of blocks in the disk"); } if (BlockSize == 0) { throw new ValidationException(nameof(BlockSize), "Can't be zero."); } if (BlockSize % 512 != 0) { throw new ValidationException(nameof(BlockSize), "Must be a multiple of 512"); } if (VHD.ToDiskTimestamp(ParentTimeStamp) < int.MinValue || VHD.ToDiskTimestamp(ParentTimeStamp) > int.MaxValue) { throw new ValidationException(nameof(ParentTimeStamp), $"Must be at most {int.MinValue} - {int.MaxValue} seconds away from 2000-01-01 00:00:00 UTC"); } if (Reserved1 == null || Reserved1.Length != RESERVED1_FIELD_SIZE || Reserved1.Any(m => m > 0)) { throw new ValidationException(nameof(Reserved1), $"Must be {RESERVED1_FIELD_SIZE} nullbytes"); } if (Checksum != ComputeChecksum()) { throw new ValidationException(nameof(Checksum), $"Wrong checksum. Expected: {ComputeChecksum()}"); } if (ParentUnicodeName == null || Encoding.Unicode.GetBytes(ParentUnicodeName).Length != UNICODE_PARENT_LENGTH) { throw new ValidationException(nameof(ParentUnicodeName), $"Must be a {UNICODE_PARENT_LENGTH} byte UTF-16 string. Pad with nullbytes as needed"); } if (ParentLocatorEntries == null || ParentLocatorEntries.Length != LOCATOR_ENTRY_COUNT) { throw new ValidationException(nameof(ParentLocatorEntries), $"Must contain {LOCATOR_ENTRY_COUNT} entries"); } for (var i = 0; i < LOCATOR_ENTRY_COUNT; i++) { try { ParentLocatorEntries[i].Validate(); } catch (Exception ex) { throw new ValidationException(nameof(ParentLocatorEntries), $"Element at index {i} failed to validate. See inner exception for details.", ex); } } if (Reserved2 == null || Reserved2.Length != RESERVED2_FIELD_SIZE || Reserved2.Any(m => m > 0)) { throw new ValidationException(nameof(Reserved2), $"Must be {RESERVED2_FIELD_SIZE} nullbytes"); } }
/// <summary> /// Validates the header and throws an exception if it's invalid /// </summary> public void Validate() { var E = Encoding.Default; if (Cookie == null || E.GetBytes(Cookie).Length != 8) { throw new ValidationException(nameof(Cookie), "Must be an 8 byte (ANSI) string"); } if (!Enum.IsDefined(typeof(VhdFeatures), Features)) { throw new ValidationException(nameof(Features), "Must be one or a combination of the defined enum values"); } if (!Features.HasFlag(VhdFeatures.Reserved)) { throw new ValidationException(nameof(Features), $"Must have '{nameof(VhdFeatures.Reserved)}' flag set"); } if ((int)Features >= ((int)VhdFeatures.Reserved << 1)) { throw new ValidationException(nameof(Features), "Must have undefined bits set to zero"); } if (FileFormatVersion == null) { throw new ValidationException(nameof(FileFormatVersion), "Must be defined"); } if (!Tools.InRange(ushort.MinValue, FileFormatVersion.Major, ushort.MaxValue)) { throw new ValidationException(nameof(FileFormatVersion) + "." + nameof(Version.Major), $"Must be in the range of {ushort.MinValue}-{ushort.MaxValue}"); } if (!Tools.InRange(ushort.MinValue, FileFormatVersion.Major, ushort.MaxValue)) { throw new ValidationException(nameof(FileFormatVersion) + "." + nameof(Version.Minor), $"Must be in the range of {ushort.MinValue}-{ushort.MaxValue}"); } if (DiskType == VhdType.FixedDisk && DataOffset != OFFSET_NONE) { throw new ValidationException(nameof(DataOffset), $"Must be set to ({nameof(OFFSET_NONE)}){OFFSET_NONE} for a fixed vhd type"); } if (VHD.ToDiskTimestamp(TimeStamp) < int.MinValue || VHD.ToDiskTimestamp(TimeStamp) > int.MaxValue) { throw new ValidationException(nameof(TimeStamp), $"Must be at most {int.MinValue}-{int.MaxValue} seconds away from 2000-01-01 00:00:00 UTC"); } if (CreatorApplication == null || E.GetBytes(CreatorApplication).Length != 4) { throw new ValidationException(nameof(CreatorApplication), "Must be a 4 byte (ANSI) string"); } if (CreatorVersion == null) { throw new ValidationException(nameof(CreatorVersion), "Must be defined"); } if (!Tools.InRange(ushort.MinValue, CreatorVersion.Major, ushort.MaxValue)) { throw new ValidationException(nameof(CreatorVersion) + nameof(Version.Major), $"Must be in the range of {ushort.MinValue}-{ushort.MaxValue}"); } if (!Tools.InRange(ushort.MinValue, CreatorVersion.Minor, ushort.MaxValue)) { throw new ValidationException(nameof(CreatorVersion) + nameof(Version.Minor), $"Must be in the range of {ushort.MinValue}-{ushort.MaxValue}"); } if (CreatorHostOS == null || E.GetBytes(CreatorHostOS).Length != 4) { throw new ValidationException(nameof(CreatorHostOS), "Is not a 4 byte (ANSI) string"); } if (OriginalSize == 0) { throw new ValidationException(nameof(OriginalSize), "Can't be zero"); } if (CurrentSize == 0) { throw new ValidationException(nameof(CurrentSize), $"Can't be zero"); } if (CurrentSize % 512 != 0) { throw new ValidationException(nameof(CurrentSize), "Must be a multiple of 512"); } if (DiskGeometry == null) { throw new ValidationException(nameof(DiskGeometry), "Is not defined"); } if (DiskGeometry.Cylinders < 1 || DiskGeometry.Cylinders > CHS.MAX_CYLINDERS) { throw new ValidationException(nameof(DiskGeometry) + nameof(CHS.Cylinders), $"Must be in the range of 1-{CHS.MAX_CYLINDERS}"); } if (DiskGeometry.Heads < 1 || DiskGeometry.Heads > CHS.MAX_HEADS) { throw new ValidationException(nameof(DiskGeometry) + nameof(CHS.Heads), $"Must be in the range of 1-{CHS.MAX_HEADS}"); } if (DiskGeometry.SectorsPerTrack < 1 || DiskGeometry.SectorsPerTrack > CHS.MAX_SECTORS_PER_TRACK) { throw new ValidationException(nameof(DiskGeometry) + nameof(CHS.SectorsPerTrack), $"Must be in the range of 1-{CHS.MAX_SECTORS_PER_TRACK}"); } if (!DiskGeometry.Equals(new CHS(CurrentSize))) { throw new ValidationException(nameof(DiskGeometry), $"Does not matches {nameof(CurrentSize)}"); } if (!Enum.IsDefined(typeof(VhdType), DiskType)) { throw new ValidationException(nameof(DiskType), "Not one of the defined enum values"); } if (Checksum != ComputeChecksum()) { throw new ValidationException(nameof(Checksum), $"Wrong checksum. Expected: {ComputeChecksum()}"); } if (DiskId == Guid.Empty) { throw new ValidationException(nameof(DiskId), "Is invalid (All zeros)"); } if (Reserved == null || Reserved.Length != RESERVED_FIELD_SIZE) { throw new ValidationException(nameof(Reserved), $"Must be {RESERVED_FIELD_SIZE} bytes long"); } if (Reserved.Any(m => m != 0)) { throw new ValidationException(nameof(Reserved), "Must be made up of nullbytes only"); } }