From a230871f757e191e980d0d5e9a3ca5569e08183b Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Thu, 12 Jan 2023 12:28:31 -0800 Subject: [PATCH] Add AACS media block builder --- BurnOutSharp.Builders/AACS.cs | 447 ++++++++++++++++++ BurnOutSharp.Models/AACS/CopyrightRecord.cs | 13 + .../AACS/DriveRevocationListEntry.cs | 21 + .../AACS/DriveRevocationListRecord.cs | 26 + .../AACS/DriveRevocationSignatureBlock.cs | 17 + .../AACS/EndOfMediaKeyBlockRecord.cs | 2 +- BurnOutSharp.Models/AACS/Enums.cs | 34 ++ .../AACS/ExplicitSubsetDifferenceRecord.cs | 2 +- .../AACS/HostRevocationListEntry.cs | 21 + .../AACS/HostRevocationListRecord.cs | 29 ++ .../AACS/HostRevocationSignatureBlock.cs | 17 + BurnOutSharp.Models/AACS/MediaKeyBlock.cs | 2 +- .../AACS/MediaKeyDataRecord.cs | 2 +- BurnOutSharp.Models/AACS/Record.cs | 2 +- BurnOutSharp.Models/AACS/SubsetDifference.cs | 2 +- .../AACS/SubsetDifferenceIndexRecord.cs | 2 +- .../AACS/TypeAndVersionRecord.cs | 10 +- .../AACS/VerifyMediaKeyRecord.cs | 2 +- BurnOutSharp.Utilities/Extensions.cs | 77 +++ 19 files changed, 716 insertions(+), 12 deletions(-) create mode 100644 BurnOutSharp.Builders/AACS.cs create mode 100644 BurnOutSharp.Models/AACS/CopyrightRecord.cs create mode 100644 BurnOutSharp.Models/AACS/DriveRevocationListEntry.cs create mode 100644 BurnOutSharp.Models/AACS/DriveRevocationListRecord.cs create mode 100644 BurnOutSharp.Models/AACS/DriveRevocationSignatureBlock.cs create mode 100644 BurnOutSharp.Models/AACS/HostRevocationListEntry.cs create mode 100644 BurnOutSharp.Models/AACS/HostRevocationListRecord.cs create mode 100644 BurnOutSharp.Models/AACS/HostRevocationSignatureBlock.cs diff --git a/BurnOutSharp.Builders/AACS.cs b/BurnOutSharp.Builders/AACS.cs new file mode 100644 index 00000000..fdb8634a --- /dev/null +++ b/BurnOutSharp.Builders/AACS.cs @@ -0,0 +1,447 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using BurnOutSharp.Models.AACS; +using BurnOutSharp.Utilities; + +namespace BurnOutSharp.Builders +{ + public class AACS + { + #region Byte Data + + /// + /// Parse a byte array into an AACS media key block + /// + /// Byte array to parse + /// Offset into the byte array + /// Filled archive on success, null on error + public static MediaKeyBlock ParseMediaKeyBlock(byte[] data, int offset) + { + // If the data is invalid + if (data == null) + return null; + + // If the offset is out of bounds + if (offset < 0 || offset >= data.Length) + return null; + + // Create a memory stream and parse that + MemoryStream dataStream = new MemoryStream(data, offset, data.Length - offset); + return ParseMediaKeyBlock(dataStream); + } + + #endregion + + #region Stream Data + + /// + /// Parse a Stream into an AACS media key block + /// + /// Stream to parse + /// Filled cmedia key block on success, null on error + public static MediaKeyBlock ParseMediaKeyBlock(Stream data) + { + // If the data is invalid + if (data == null || data.Length == 0 || !data.CanSeek || !data.CanRead) + return null; + + // If the offset is out of bounds + if (data.Position < 0 || data.Position >= data.Length) + return null; + + // Cache the current offset + int initialOffset = (int)data.Position; + + // Create a new media key block to fill + var mediaKeyBlock = new MediaKeyBlock(); + + #region Records + + // Create the records list + var records = new List(); + + // Try to parse the records + while (data.Position < data.Length) + { + // Try to parse the record + var record = ParseRecord(data); + if (record == null) + return null; + + // Add the record + records.Add(record); + + // If we have an end of media key block record + if (record.RecordType == RecordType.EndOfMediaKeyBlock) + break; + + // Align to the 4-byte boundary if we're not at the end + if (data.Position != data.Length) + { + while ((data.Position % 4) != 0) + _ = data.ReadByteValue(); + } + else + { + break; + } + } + + // Set the records + mediaKeyBlock.Records = records.ToArray(); + + #endregion + + return mediaKeyBlock; + } + + /// + /// Parse a Stream into a record + /// + /// Stream to parse + /// Filled record on success, null on error + private static Record ParseRecord(Stream data) + { + // TODO: Use marshalling here instead of building + + // The first 4 bytes make up the type and length + byte[] typeAndLength = data.ReadBytes(4); + RecordType type = (RecordType)typeAndLength[0]; + + // Remove the first byte and parse as big-endian + typeAndLength[0] = 0x00; + Array.Reverse(typeAndLength); + uint length = BitConverter.ToUInt32(typeAndLength, 0); + + // Create a record based on the type + switch (type) + { + // Recognized record types + case RecordType.EndOfMediaKeyBlock: return ParseEndOfMediaKeyBlockRecord(data, type, length); + case RecordType.ExplicitSubsetDifference: return ParseExplicitSubsetDifferenceRecord(data, type, length); + case RecordType.MediaKeyData: return ParseMediaKeyDataRecord(data, type, length); + case RecordType.SubsetDifferenceIndex: return ParseSubsetDifferenceIndexRecord(data, type, length); + case RecordType.TypeAndVersion: return ParseTypeAndVersionRecord(data, type, length); + case RecordType.DriveRevocationList: return ParseDriveRevocationListRecord(data, type, length); + case RecordType.HostRevocationList: return ParseHostRevocationListRecord(data, type, length); + case RecordType.VerifyMediaKey: return ParseVerifyMediaKeyRecord(data, type, length); + case RecordType.Copyright: return ParseCopyrightRecord(data, type, length); + + // Unrecognized record type + default: + return null; + } + } + + /// + /// Parse a Stream into an end of media key block record + /// + /// Stream to parse + /// Filled end of media key block record on success, null on error + private static EndOfMediaKeyBlockRecord ParseEndOfMediaKeyBlockRecord(Stream data, RecordType type, uint length) + { + // Verify we're calling the right parser + if (type != RecordType.EndOfMediaKeyBlock) + return null; + + // TODO: Use marshalling here instead of building + var record = new EndOfMediaKeyBlockRecord(); + + record.RecordType = type; + record.RecordLength = length; + if (length > 4) + record.SignatureData = data.ReadBytes((int)(length - 4)); + + return record; + } + + /// + /// Parse a Stream into an explicit subset-difference record + /// + /// Stream to parse + /// Filled explicit subset-difference record on success, null on error + private static ExplicitSubsetDifferenceRecord ParseExplicitSubsetDifferenceRecord(Stream data, RecordType type, uint length) + { + // Verify we're calling the right parser + if (type != RecordType.ExplicitSubsetDifference) + return null; + + // TODO: Use marshalling here instead of building + var record = new ExplicitSubsetDifferenceRecord(); + + // Cache the current offset + long initialOffset = data.Position - 4; + + // Create the subset difference list + var subsetDifferences = new List(); + + // Try to parse the subset differences + while (data.Position < initialOffset + length - 5) + { + var subsetDifference = new SubsetDifference(); + + subsetDifference.Mask = data.ReadByteValue(); + subsetDifference.Number = data.ReadUInt32BE(); + + subsetDifferences.Add(subsetDifference); + } + + // Set the subset differences + record.SubsetDifferences = subsetDifferences.ToArray(); + + return record; + } + + /// + /// Parse a Stream into a media key data record + /// + /// Stream to parse + /// Filled media key data record on success, null on error + private static MediaKeyDataRecord ParseMediaKeyDataRecord(Stream data, RecordType type, uint length) + { + // Verify we're calling the right parser + if (type != RecordType.MediaKeyData) + return null; + + // TODO: Use marshalling here instead of building + var record = new MediaKeyDataRecord(); + + // Cache the current offset + long initialOffset = data.Position - 4; + + // Create the media key list + var mediaKeys = new List(); + + // Try to parse the media keys + while (data.Position < initialOffset + length) + { + byte[] mediaKey = data.ReadBytes(0x10); + mediaKeys.Add(mediaKey); + } + + // Set the media keys + record.MediaKeyData = mediaKeys.ToArray(); + + return record; + } + + /// + /// Parse a Stream into a subset-difference index record + /// + /// Stream to parse + /// Filled subset-difference index record on success, null on error + private static SubsetDifferenceIndexRecord ParseSubsetDifferenceIndexRecord(Stream data, RecordType type, uint length) + { + // Verify we're calling the right parser + if (type != RecordType.SubsetDifferenceIndex) + return null; + + // TODO: Use marshalling here instead of building + var record = new SubsetDifferenceIndexRecord(); + + // Cache the current offset + long initialOffset = data.Position - 4; + + record.Span = data.ReadUInt32BE(); + + // Create the offset list + var offsets = new List(); + + // Try to parse the offsets + while (data.Position < initialOffset + length) + { + uint offset = data.ReadUInt32BE(); + offsets.Add(offset); + } + + // Set the offsets + record.Offsets = offsets.ToArray(); + + return record; + } + + /// + /// Parse a Stream into a type and version record + /// + /// Stream to parse + /// Filled type and version record on success, null on error + private static TypeAndVersionRecord ParseTypeAndVersionRecord(Stream data, RecordType type, uint length) + { + // Verify we're calling the right parser + if (type != RecordType.TypeAndVersion) + return null; + + // TODO: Use marshalling here instead of building + var record = new TypeAndVersionRecord(); + + record.RecordType = type; + record.RecordLength = length; + record.MKBType = (MediaKeyBlockType)data.ReadUInt32BE(); + record.VersionNumber = data.ReadUInt32BE(); + + return record; + } + + /// + /// Parse a Stream into a drive revocation list record + /// + /// Stream to parse + /// Filled drive revocation list record on success, null on error + private static DriveRevocationListRecord ParseDriveRevocationListRecord(Stream data, RecordType type, uint length) + { + // Verify we're calling the right parser + if (type != RecordType.DriveRevocationList) + return null; + + // TODO: Use marshalling here instead of building + var record = new DriveRevocationListRecord(); + + // Cache the current offset + long initialOffset = data.Position - 4; + + record.TotalNumberOfEntries = data.ReadUInt32BE(); + + // Create the signature blocks list + var blocks = new List(); + + // Try to parse the signature blocks + while (data.Position < initialOffset + length) + { + var block = new DriveRevocationSignatureBlock(); + + block.NumberOfEntries = data.ReadUInt32BE(); + block.EntryFields = new DriveRevocationListEntry[block.NumberOfEntries]; + for (int i = 0; i < block.EntryFields.Length; i++) + { + var entry = new DriveRevocationListEntry(); + + entry.Range = data.ReadUInt16BE(); + entry.DriveID = data.ReadBytes(6); + + block.EntryFields[i] = entry; + } + + blocks.Add(block); + + // If we have an empty block + if (block.NumberOfEntries == 0) + break; + } + + // Set the signature blocks + record.SignatureBlocks = blocks.ToArray(); + + // If there's any data left, discard it + if (data.Position < initialOffset + length) + _ = data.ReadBytes((int)(initialOffset + length - data.Position)); + + return record; + } + + /// + /// Parse a Stream into a host revocation list record + /// + /// Stream to parse + /// Filled host revocation list record on success, null on error + private static HostRevocationListRecord ParseHostRevocationListRecord(Stream data, RecordType type, uint length) + { + // Verify we're calling the right parser + if (type != RecordType.HostRevocationList) + return null; + + // TODO: Use marshalling here instead of building + var record = new HostRevocationListRecord(); + + // Cache the current offset + long initialOffset = data.Position - 4; + + record.TotalNumberOfEntries = data.ReadUInt32BE(); + + // Create the signature blocks list + var blocks = new List(); + + // Try to parse the signature blocks + while (data.Position < initialOffset + length) + { + var block = new HostRevocationSignatureBlock(); + + block.NumberOfEntries = data.ReadUInt32BE(); + block.EntryFields = new HostRevocationListEntry[block.NumberOfEntries]; + for (int i = 0; i < block.EntryFields.Length; i++) + { + var entry = new HostRevocationListEntry(); + + entry.Range = data.ReadUInt16BE(); + entry.HostID = data.ReadBytes(6); + + block.EntryFields[i] = entry; + } + + blocks.Add(block); + + // If we have an empty block + if (block.NumberOfEntries == 0) + break; + } + + // Set the signature blocks + record.SignatureBlocks = blocks.ToArray(); + + // If there's any data left, discard it + if (data.Position < initialOffset + length) + _ = data.ReadBytes((int)(initialOffset + length - data.Position)); + + return record; + } + + /// + /// Parse a Stream into a verify media key record + /// + /// Stream to parse + /// Filled verify media key record on success, null on error + private static VerifyMediaKeyRecord ParseVerifyMediaKeyRecord(Stream data, RecordType type, uint length) + { + // Verify we're calling the right parser + if (type != RecordType.VerifyMediaKey) + return null; + + // TODO: Use marshalling here instead of building + var record = new VerifyMediaKeyRecord(); + + record.RecordType = type; + record.RecordLength = length; + record.CiphertextValue = data.ReadBytes(0x10); + + return record; + } + + /// + /// Parse a Stream into a copyright record + /// + /// Stream to parse + /// Filled copyright record on success, null on error + private static CopyrightRecord ParseCopyrightRecord(Stream data, RecordType type, uint length) + { + // Verify we're calling the right parser + if (type != RecordType.Copyright) + return null; + + // TODO: Use marshalling here instead of building + var record = new CopyrightRecord(); + + record.RecordType = type; + record.RecordLength = length; + if (length > 4) + { + byte[] copyright = data.ReadBytes((int)(length - 4)); + record.Copyright = Encoding.ASCII.GetString(copyright).TrimEnd('\0'); + } + + return record; + } + + #endregion + } +} diff --git a/BurnOutSharp.Models/AACS/CopyrightRecord.cs b/BurnOutSharp.Models/AACS/CopyrightRecord.cs new file mode 100644 index 00000000..9ee422a2 --- /dev/null +++ b/BurnOutSharp.Models/AACS/CopyrightRecord.cs @@ -0,0 +1,13 @@ +namespace BurnOutSharp.Models.AACS +{ + /// + /// This record type is undocumented but found in real media key blocks + /// + public sealed class CopyrightRecord : Record + { + /// + /// Null-terminated ASCII string representing the copyright + /// + public string Copyright; + } +} \ No newline at end of file diff --git a/BurnOutSharp.Models/AACS/DriveRevocationListEntry.cs b/BurnOutSharp.Models/AACS/DriveRevocationListEntry.cs new file mode 100644 index 00000000..730c158f --- /dev/null +++ b/BurnOutSharp.Models/AACS/DriveRevocationListEntry.cs @@ -0,0 +1,21 @@ +namespace BurnOutSharp.Models.AACS +{ + /// + public sealed class DriveRevocationListEntry + { + /// + /// A 2-byte Range value indicates the range of revoked ID’s starting + /// from the ID contained in the record. A value of zero in the Range + /// field indicates that only one ID is being revoked, a value of one + /// in the Range field indicates two ID’s are being revoked, and so on. + /// + public ushort Range; + + /// + /// A 6-byte Drive ID value identifying the Licensed Drive being revoked + /// (or the first in a range of Licensed Drives being revoked, in the + /// case of a non-zero Range value). + /// + public byte[] DriveID; + } +} \ No newline at end of file diff --git a/BurnOutSharp.Models/AACS/DriveRevocationListRecord.cs b/BurnOutSharp.Models/AACS/DriveRevocationListRecord.cs new file mode 100644 index 00000000..8d424db8 --- /dev/null +++ b/BurnOutSharp.Models/AACS/DriveRevocationListRecord.cs @@ -0,0 +1,26 @@ +namespace BurnOutSharp.Models.AACS +{ + /// + /// A properly formatted type 3 or type 4 Media Key Block contains exactly + /// one Drive Revocation List Record. It follows the Host Revocation List + /// Record, although it may not immediately follow it. + /// + /// The Drive Revocation List Record is identical to the Host Revocation + /// List Record, except it has type 2016, and it contains Drive Revocation + /// List Entries, not Host Revocation List Entries. The Drive Revocation List + /// Entries refer to Drive IDs in the Drive Certificates. + /// + /// + public sealed class DriveRevocationListRecord : Record + { + /// + /// The total number of Drive Revocation List Entry fields that follow. + /// + public uint TotalNumberOfEntries; + + /// + /// Revocation list entries + /// + public DriveRevocationSignatureBlock[] SignatureBlocks; + } +} \ No newline at end of file diff --git a/BurnOutSharp.Models/AACS/DriveRevocationSignatureBlock.cs b/BurnOutSharp.Models/AACS/DriveRevocationSignatureBlock.cs new file mode 100644 index 00000000..0aaa6220 --- /dev/null +++ b/BurnOutSharp.Models/AACS/DriveRevocationSignatureBlock.cs @@ -0,0 +1,17 @@ +namespace BurnOutSharp.Models.AACS +{ + /// + public sealed class DriveRevocationSignatureBlock + { + /// + /// The number of Drive Revocation List Entry fields in the signature block. + /// + public uint NumberOfEntries; + + /// + /// A list of 8-byte Host Drive List Entry fields, the length of this + /// list being equal to the number in the signature block. + /// + public DriveRevocationListEntry[] EntryFields; + } +} \ No newline at end of file diff --git a/BurnOutSharp.Models/AACS/EndOfMediaKeyBlockRecord.cs b/BurnOutSharp.Models/AACS/EndOfMediaKeyBlockRecord.cs index 1646cccf..3782ce13 100644 --- a/BurnOutSharp.Models/AACS/EndOfMediaKeyBlockRecord.cs +++ b/BurnOutSharp.Models/AACS/EndOfMediaKeyBlockRecord.cs @@ -7,7 +7,7 @@ namespace BurnOutSharp.Models.AACS /// that MKB (pending possible checks for correctness of the key, as /// described previously). /// - /// + /// public sealed class EndOfMediaKeyBlockRecord : Record { /// diff --git a/BurnOutSharp.Models/AACS/Enums.cs b/BurnOutSharp.Models/AACS/Enums.cs index 7403264c..0fdebc6d 100644 --- a/BurnOutSharp.Models/AACS/Enums.cs +++ b/BurnOutSharp.Models/AACS/Enums.cs @@ -1,5 +1,34 @@ namespace BurnOutSharp.Models.AACS { + public enum MediaKeyBlockType : uint + { + /// + /// (Type 3). This is a normal Media Key Block suitable for being recorded + /// on a AACS Recordable Media. Both Class I and Class II Licensed Products + /// use it to directly calculate the Media Key. + /// + Type3 = 0x00031003, + + /// + /// (Type 4). This is a Media Key Block that has been designed to use Key + /// Conversion Data (KCD). Thus, it is suitable only for pre-recorded media + /// from which the KCD is derived. Both Class I and Class II Licensed Products + /// use it to directly calculate the Media Key. + /// + Type4 = 0x00041003, + + /// + /// (Type 10). This is a Class II Media Key Block (one that has the functionality + /// of a Sequence Key Block). This can only be processed by Class II Licensed + /// Products; Class I Licensed Products are revoked in Type 10 Media Key Blocks + /// and cannot process them. This type does not contain the Host Revocation List + /// Record, the Drive Revocation List Record, and the Media Key Data Record, as + /// described in the following sections. It does contain the records shown in + /// Section 3.2.5.2, which are only processed by Class II Licensed Products. + /// + Type10 = 0x000A1003, + } + public enum RecordType : byte { EndOfMediaKeyBlock = 0x02, @@ -7,6 +36,11 @@ namespace BurnOutSharp.Models.AACS MediaKeyData = 0x05, SubsetDifferenceIndex = 0x07, TypeAndVersion = 0x10, + DriveRevocationList = 0x20, + HostRevocationList = 0x21, VerifyMediaKey = 0x81, + + // Not documented + Copyright = 0x7F, } } \ No newline at end of file diff --git a/BurnOutSharp.Models/AACS/ExplicitSubsetDifferenceRecord.cs b/BurnOutSharp.Models/AACS/ExplicitSubsetDifferenceRecord.cs index 1e638881..7479ee94 100644 --- a/BurnOutSharp.Models/AACS/ExplicitSubsetDifferenceRecord.cs +++ b/BurnOutSharp.Models/AACS/ExplicitSubsetDifferenceRecord.cs @@ -1,6 +1,6 @@ namespace BurnOutSharp.Models.AACS { - /// + /// public sealed class ExplicitSubsetDifferenceRecord : Record { /// diff --git a/BurnOutSharp.Models/AACS/HostRevocationListEntry.cs b/BurnOutSharp.Models/AACS/HostRevocationListEntry.cs new file mode 100644 index 00000000..05d4e0d6 --- /dev/null +++ b/BurnOutSharp.Models/AACS/HostRevocationListEntry.cs @@ -0,0 +1,21 @@ +namespace BurnOutSharp.Models.AACS +{ + /// + public sealed class HostRevocationListEntry + { + /// + /// A 2-byte Range value indicates the range of revoked ID’s starting + /// from the ID contained in the record. A value of zero in the Range + /// field indicates that only one ID is being revoked, a value of one + /// in the Range field indicates two ID’s are being revoked, and so on. + /// + public ushort Range; + + /// + /// A 6-byte Host ID value identifying the host being revoked (or the + /// first in a range of hosts being revoked, in the case of a non-zero + /// Range value). + /// + public byte[] HostID; + } +} \ No newline at end of file diff --git a/BurnOutSharp.Models/AACS/HostRevocationListRecord.cs b/BurnOutSharp.Models/AACS/HostRevocationListRecord.cs new file mode 100644 index 00000000..8067fdf9 --- /dev/null +++ b/BurnOutSharp.Models/AACS/HostRevocationListRecord.cs @@ -0,0 +1,29 @@ +namespace BurnOutSharp.Models.AACS +{ + /// + /// A properly formatted type 3 or type 4 Media Key Block shall have exactly + /// one Host Revocation List Record as its second record. This record provides + /// a list of hosts that have been revoked by the AACS LA. The AACS specification + /// is applicable to PC-based system where a Licensed Drive and PC Host act + /// together as the Recording Device and/or Playback Device for AACS Content. + /// AACS uses a drive-host authentication protocol for the host to verify the + /// integrity of the data received from the Licensed Drive, and for the Licensed + /// Drive to check the validity of the host application. The Type and Version + /// Record and the Host Revocation List Record are guaranteed to be the first two + /// records of a Media Key Block, to make it easier for Licensed Drives to extract + /// this data from an arbitrary Media Key Block. + /// + /// + public sealed class HostRevocationListRecord : Record + { + /// + /// The total number of Host Revocation List Entry fields that follow. + /// + public uint TotalNumberOfEntries; + + /// + /// Revocation list entries + /// + public HostRevocationSignatureBlock[] SignatureBlocks; + } +} \ No newline at end of file diff --git a/BurnOutSharp.Models/AACS/HostRevocationSignatureBlock.cs b/BurnOutSharp.Models/AACS/HostRevocationSignatureBlock.cs new file mode 100644 index 00000000..31c4352c --- /dev/null +++ b/BurnOutSharp.Models/AACS/HostRevocationSignatureBlock.cs @@ -0,0 +1,17 @@ +namespace BurnOutSharp.Models.AACS +{ + /// + public sealed class HostRevocationSignatureBlock + { + /// + /// The number of Host Revocation List Entry fields in the signature block. + /// + public uint NumberOfEntries; + + /// + /// A list of 8-byte Host Revocation List Entry fields, the length of this + /// list being equal to the number in the signature block. + /// + public HostRevocationListEntry[] EntryFields; + } +} \ No newline at end of file diff --git a/BurnOutSharp.Models/AACS/MediaKeyBlock.cs b/BurnOutSharp.Models/AACS/MediaKeyBlock.cs index 72e28a46..0061a201 100644 --- a/BurnOutSharp.Models/AACS/MediaKeyBlock.cs +++ b/BurnOutSharp.Models/AACS/MediaKeyBlock.cs @@ -3,7 +3,7 @@ namespace BurnOutSharp.Models.AACS /// /// A Media Key Block is formatted as a sequence of contiguous Records. /// - /// + /// public sealed class MediaKeyBlock { /// diff --git a/BurnOutSharp.Models/AACS/MediaKeyDataRecord.cs b/BurnOutSharp.Models/AACS/MediaKeyDataRecord.cs index 91af9b0a..8db271e4 100644 --- a/BurnOutSharp.Models/AACS/MediaKeyDataRecord.cs +++ b/BurnOutSharp.Models/AACS/MediaKeyDataRecord.cs @@ -4,7 +4,7 @@ namespace BurnOutSharp.Models.AACS /// This record gives the associated encrypted media key data for the /// subset-differences identified in the Explicit Subset-Difference Record. /// - /// + /// public sealed class MediaKeyDataRecord : Record { /// diff --git a/BurnOutSharp.Models/AACS/Record.cs b/BurnOutSharp.Models/AACS/Record.cs index 0c86ec40..3ac307b5 100644 --- a/BurnOutSharp.Models/AACS/Record.cs +++ b/BurnOutSharp.Models/AACS/Record.cs @@ -9,7 +9,7 @@ namespace BurnOutSharp.Models.AACS /// the length field, are “Big Endian”; in other words, the most significant /// byte comes first in the record. /// - /// + /// public abstract class Record { /// diff --git a/BurnOutSharp.Models/AACS/SubsetDifference.cs b/BurnOutSharp.Models/AACS/SubsetDifference.cs index 53e63671..987e206f 100644 --- a/BurnOutSharp.Models/AACS/SubsetDifference.cs +++ b/BurnOutSharp.Models/AACS/SubsetDifference.cs @@ -1,6 +1,6 @@ namespace BurnOutSharp.Models.AACS { - /// + /// public sealed class SubsetDifference { /// diff --git a/BurnOutSharp.Models/AACS/SubsetDifferenceIndexRecord.cs b/BurnOutSharp.Models/AACS/SubsetDifferenceIndexRecord.cs index 4428c245..1c4f818a 100644 --- a/BurnOutSharp.Models/AACS/SubsetDifferenceIndexRecord.cs +++ b/BurnOutSharp.Models/AACS/SubsetDifferenceIndexRecord.cs @@ -8,7 +8,7 @@ namespace BurnOutSharp.Models.AACS /// is always present, and always precedes the Explicit Subset-Difference record /// in the MKB, although it does not necessarily immediately precede it. /// - /// + /// public sealed class SubsetDifferenceIndexRecord : Record { /// diff --git a/BurnOutSharp.Models/AACS/TypeAndVersionRecord.cs b/BurnOutSharp.Models/AACS/TypeAndVersionRecord.cs index 8ca2d210..7310610a 100644 --- a/BurnOutSharp.Models/AACS/TypeAndVersionRecord.cs +++ b/BurnOutSharp.Models/AACS/TypeAndVersionRecord.cs @@ -8,14 +8,16 @@ namespace BurnOutSharp.Models.AACS /// fact, more recent than the Media Key Block Extension that is currently /// on the media. /// - /// + /// public sealed class TypeAndVersionRecord : Record { /// - /// For AACS applications, the type field is always 0x00031003. - /// Devices shall ignore this. + /// For AACS applications, the MKBType field is one of three values. + /// It is not an error for a Type 3 Media Key Block to be used for + /// controlling access to AACS Content on pre- recorded media. In + /// this case, the device shall not use the KCD. /// - public uint MKBType; + public MediaKeyBlockType MKBType; /// /// The Version Number is a 32-bit unsigned integer. Each time the diff --git a/BurnOutSharp.Models/AACS/VerifyMediaKeyRecord.cs b/BurnOutSharp.Models/AACS/VerifyMediaKeyRecord.cs index 0eba733f..3efc74b5 100644 --- a/BurnOutSharp.Models/AACS/VerifyMediaKeyRecord.cs +++ b/BurnOutSharp.Models/AACS/VerifyMediaKeyRecord.cs @@ -10,7 +10,7 @@ namespace BurnOutSharp.Models.AACS /// [AES_128D(vKm, C]msb_64 == 0x0123456789ABCDEF)] /// where Km is the Media Key value. /// - /// + /// public sealed class VerifyMediaKeyRecord : Record { /// diff --git a/BurnOutSharp.Utilities/Extensions.cs b/BurnOutSharp.Utilities/Extensions.cs index be63499a..5949d91e 100644 --- a/BurnOutSharp.Utilities/Extensions.cs +++ b/BurnOutSharp.Utilities/Extensions.cs @@ -370,6 +370,17 @@ namespace BurnOutSharp.Utilities return BitConverter.ToInt16(buffer, 0); } + /// + /// Read a short from the stream in big-endian format + /// + public static short ReadInt16BE(this Stream stream) + { + byte[] buffer = new byte[2]; + stream.Read(buffer, 0, 2); + Array.Reverse(buffer); + return BitConverter.ToInt16(buffer, 0); + } + /// /// Read a ushort from the stream /// @@ -380,6 +391,17 @@ namespace BurnOutSharp.Utilities return BitConverter.ToUInt16(buffer, 0); } + /// + /// Read a ushort from the stream in big-endian format + /// + public static ushort ReadUInt16BE(this Stream stream) + { + byte[] buffer = new byte[2]; + stream.Read(buffer, 0, 2); + Array.Reverse(buffer); + return BitConverter.ToUInt16(buffer, 0); + } + /// /// Read an int from the stream /// @@ -390,6 +412,17 @@ namespace BurnOutSharp.Utilities return BitConverter.ToInt32(buffer, 0); } + /// + /// Read an int from the stream in big-endian format + /// + public static int ReadInt32BE(this Stream stream) + { + byte[] buffer = new byte[4]; + stream.Read(buffer, 0, 4); + Array.Reverse(buffer); + return BitConverter.ToInt32(buffer, 0); + } + /// /// Read a uint from the stream /// @@ -400,6 +433,17 @@ namespace BurnOutSharp.Utilities return BitConverter.ToUInt32(buffer, 0); } + /// + /// Read a uint from the stream in big-endian format + /// + public static uint ReadUInt32BE(this Stream stream) + { + byte[] buffer = new byte[4]; + stream.Read(buffer, 0, 4); + Array.Reverse(buffer); + return BitConverter.ToUInt32(buffer, 0); + } + /// /// Read a long from the stream /// @@ -410,6 +454,17 @@ namespace BurnOutSharp.Utilities return BitConverter.ToInt64(buffer, 0); } + /// + /// Read a long from the stream in big-endian format + /// + public static long ReadInt64BE(this Stream stream) + { + byte[] buffer = new byte[8]; + stream.Read(buffer, 0, 8); + Array.Reverse(buffer); + return BitConverter.ToInt64(buffer, 0); + } + /// /// Read a ulong from the stream /// @@ -420,6 +475,17 @@ namespace BurnOutSharp.Utilities return BitConverter.ToUInt64(buffer, 0); } + /// + /// Read a ulong from the stream in big-endian format + /// + public static ulong ReadUInt64BE(this Stream stream) + { + byte[] buffer = new byte[8]; + stream.Read(buffer, 0, 8); + Array.Reverse(buffer); + return BitConverter.ToUInt64(buffer, 0); + } + /// /// Read a Guid from the stream /// @@ -430,6 +496,17 @@ namespace BurnOutSharp.Utilities return new Guid(buffer); } + /// + /// Read a Guid from the stream in big-endian format + /// + public static Guid ReadGuidBE(this Stream stream) + { + byte[] buffer = new byte[16]; + stream.Read(buffer, 0, 16); + Array.Reverse(buffer); + return new Guid(buffer); + } + /// /// Read a null-terminated string from the stream ///