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
///