mirror of
https://github.com/SabreTools/BinaryObjectScanner.git
synced 2026-07-08 18:06:34 +00:00
Add AACS media block builder
This commit is contained in:
447
BurnOutSharp.Builders/AACS.cs
Normal file
447
BurnOutSharp.Builders/AACS.cs
Normal file
@@ -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
|
||||
|
||||
/// <summary>
|
||||
/// Parse a byte array into an AACS media key block
|
||||
/// </summary>
|
||||
/// <param name="data">Byte array to parse</param>
|
||||
/// <param name="offset">Offset into the byte array</param>
|
||||
/// <returns>Filled archive on success, null on error</returns>
|
||||
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
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into an AACS media key block
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled cmedia key block on success, null on error</returns>
|
||||
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<Record>();
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into a record
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled record on success, null on error</returns>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into an end of media key block record
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled end of media key block record on success, null on error</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into an explicit subset-difference record
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled explicit subset-difference record on success, null on error</returns>
|
||||
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<SubsetDifference>();
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into a media key data record
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled media key data record on success, null on error</returns>
|
||||
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<byte[]>();
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into a subset-difference index record
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled subset-difference index record on success, null on error</returns>
|
||||
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<uint>();
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into a type and version record
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled type and version record on success, null on error</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into a drive revocation list record
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled drive revocation list record on success, null on error</returns>
|
||||
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<DriveRevocationSignatureBlock>();
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into a host revocation list record
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled host revocation list record on success, null on error</returns>
|
||||
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<HostRevocationSignatureBlock>();
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into a verify media key record
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled verify media key record on success, null on error</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into a copyright record
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled copyright record on success, null on error</returns>
|
||||
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
|
||||
}
|
||||
}
|
||||
13
BurnOutSharp.Models/AACS/CopyrightRecord.cs
Normal file
13
BurnOutSharp.Models/AACS/CopyrightRecord.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
namespace BurnOutSharp.Models.AACS
|
||||
{
|
||||
/// <summary>
|
||||
/// This record type is undocumented but found in real media key blocks
|
||||
/// </summary>
|
||||
public sealed class CopyrightRecord : Record
|
||||
{
|
||||
/// <summary>
|
||||
/// Null-terminated ASCII string representing the copyright
|
||||
/// </summary>
|
||||
public string Copyright;
|
||||
}
|
||||
}
|
||||
21
BurnOutSharp.Models/AACS/DriveRevocationListEntry.cs
Normal file
21
BurnOutSharp.Models/AACS/DriveRevocationListEntry.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
namespace BurnOutSharp.Models.AACS
|
||||
{
|
||||
/// <see href="https://aacsla.com/wp-content/uploads/2019/02/AACS_Spec_Common_Final_0953.pdf"/>
|
||||
public sealed class DriveRevocationListEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public ushort Range;
|
||||
|
||||
/// <summary>
|
||||
/// 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).
|
||||
/// </summary>
|
||||
public byte[] DriveID;
|
||||
}
|
||||
}
|
||||
26
BurnOutSharp.Models/AACS/DriveRevocationListRecord.cs
Normal file
26
BurnOutSharp.Models/AACS/DriveRevocationListRecord.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
namespace BurnOutSharp.Models.AACS
|
||||
{
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <see href="https://aacsla.com/wp-content/uploads/2019/02/AACS_Spec_Common_Final_0953.pdf"/>
|
||||
public sealed class DriveRevocationListRecord : Record
|
||||
{
|
||||
/// <summary>
|
||||
/// The total number of Drive Revocation List Entry fields that follow.
|
||||
/// </summary>
|
||||
public uint TotalNumberOfEntries;
|
||||
|
||||
/// <summary>
|
||||
/// Revocation list entries
|
||||
/// </summary>
|
||||
public DriveRevocationSignatureBlock[] SignatureBlocks;
|
||||
}
|
||||
}
|
||||
17
BurnOutSharp.Models/AACS/DriveRevocationSignatureBlock.cs
Normal file
17
BurnOutSharp.Models/AACS/DriveRevocationSignatureBlock.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
namespace BurnOutSharp.Models.AACS
|
||||
{
|
||||
/// <see href="https://aacsla.com/wp-content/uploads/2019/02/AACS_Spec_Common_Final_0953.pdf"/>
|
||||
public sealed class DriveRevocationSignatureBlock
|
||||
{
|
||||
/// <summary>
|
||||
/// The number of Drive Revocation List Entry fields in the signature block.
|
||||
/// </summary>
|
||||
public uint NumberOfEntries;
|
||||
|
||||
/// <summary>
|
||||
/// A list of 8-byte Host Drive List Entry fields, the length of this
|
||||
/// list being equal to the number in the signature block.
|
||||
/// </summary>
|
||||
public DriveRevocationListEntry[] EntryFields;
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@ namespace BurnOutSharp.Models.AACS
|
||||
/// that MKB (pending possible checks for correctness of the key, as
|
||||
/// described previously).
|
||||
/// </summary>
|
||||
/// <see href="http://web.archive.org/web/20180718234519/https://aacsla.com/jp/marketplace/evaluating/aacs_technical_overview_040721.pdf"/>
|
||||
/// <see href="https://aacsla.com/wp-content/uploads/2019/02/AACS_Spec_Common_Final_0953.pdf"/>
|
||||
public sealed class EndOfMediaKeyBlockRecord : Record
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -1,5 +1,34 @@
|
||||
namespace BurnOutSharp.Models.AACS
|
||||
{
|
||||
public enum MediaKeyBlockType : uint
|
||||
{
|
||||
/// <summary>
|
||||
/// (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.
|
||||
/// </summary>
|
||||
Type3 = 0x00031003,
|
||||
|
||||
/// <summary>
|
||||
/// (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.
|
||||
/// </summary>
|
||||
Type4 = 0x00041003,
|
||||
|
||||
/// <summary>
|
||||
/// (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.
|
||||
/// </summary>
|
||||
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,
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
namespace BurnOutSharp.Models.AACS
|
||||
{
|
||||
/// <see href="http://web.archive.org/web/20180718234519/https://aacsla.com/jp/marketplace/evaluating/aacs_technical_overview_040721.pdf"/>
|
||||
/// <see href="https://aacsla.com/wp-content/uploads/2019/02/AACS_Spec_Common_Final_0953.pdf"/>
|
||||
public sealed class ExplicitSubsetDifferenceRecord : Record
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
21
BurnOutSharp.Models/AACS/HostRevocationListEntry.cs
Normal file
21
BurnOutSharp.Models/AACS/HostRevocationListEntry.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
namespace BurnOutSharp.Models.AACS
|
||||
{
|
||||
/// <see href="https://aacsla.com/wp-content/uploads/2019/02/AACS_Spec_Common_Final_0953.pdf"/>
|
||||
public sealed class HostRevocationListEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public ushort Range;
|
||||
|
||||
/// <summary>
|
||||
/// 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).
|
||||
/// </summary>
|
||||
public byte[] HostID;
|
||||
}
|
||||
}
|
||||
29
BurnOutSharp.Models/AACS/HostRevocationListRecord.cs
Normal file
29
BurnOutSharp.Models/AACS/HostRevocationListRecord.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
namespace BurnOutSharp.Models.AACS
|
||||
{
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <see href="https://aacsla.com/wp-content/uploads/2019/02/AACS_Spec_Common_Final_0953.pdf"/>
|
||||
public sealed class HostRevocationListRecord : Record
|
||||
{
|
||||
/// <summary>
|
||||
/// The total number of Host Revocation List Entry fields that follow.
|
||||
/// </summary>
|
||||
public uint TotalNumberOfEntries;
|
||||
|
||||
/// <summary>
|
||||
/// Revocation list entries
|
||||
/// </summary>
|
||||
public HostRevocationSignatureBlock[] SignatureBlocks;
|
||||
}
|
||||
}
|
||||
17
BurnOutSharp.Models/AACS/HostRevocationSignatureBlock.cs
Normal file
17
BurnOutSharp.Models/AACS/HostRevocationSignatureBlock.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
namespace BurnOutSharp.Models.AACS
|
||||
{
|
||||
/// <see href="https://aacsla.com/wp-content/uploads/2019/02/AACS_Spec_Common_Final_0953.pdf"/>
|
||||
public sealed class HostRevocationSignatureBlock
|
||||
{
|
||||
/// <summary>
|
||||
/// The number of Host Revocation List Entry fields in the signature block.
|
||||
/// </summary>
|
||||
public uint NumberOfEntries;
|
||||
|
||||
/// <summary>
|
||||
/// A list of 8-byte Host Revocation List Entry fields, the length of this
|
||||
/// list being equal to the number in the signature block.
|
||||
/// </summary>
|
||||
public HostRevocationListEntry[] EntryFields;
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ namespace BurnOutSharp.Models.AACS
|
||||
/// <summary>
|
||||
/// A Media Key Block is formatted as a sequence of contiguous Records.
|
||||
/// </summary>
|
||||
/// <see href="http://web.archive.org/web/20180718234519/https://aacsla.com/jp/marketplace/evaluating/aacs_technical_overview_040721.pdf"/>
|
||||
/// <see href="https://aacsla.com/wp-content/uploads/2019/02/AACS_Spec_Common_Final_0953.pdf"/>
|
||||
public sealed class MediaKeyBlock
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -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.
|
||||
/// </summary>
|
||||
/// <see href="http://web.archive.org/web/20180718234519/https://aacsla.com/jp/marketplace/evaluating/aacs_technical_overview_040721.pdf"/>
|
||||
/// <see href="https://aacsla.com/wp-content/uploads/2019/02/AACS_Spec_Common_Final_0953.pdf"/>
|
||||
public sealed class MediaKeyDataRecord : Record
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -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.
|
||||
/// </summary>
|
||||
/// <see href="http://web.archive.org/web/20180718234519/https://aacsla.com/jp/marketplace/evaluating/aacs_technical_overview_040721.pdf"/>
|
||||
/// <see href="https://aacsla.com/wp-content/uploads/2019/02/AACS_Spec_Common_Final_0953.pdf"/>
|
||||
public abstract class Record
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
namespace BurnOutSharp.Models.AACS
|
||||
{
|
||||
/// <see href="http://web.archive.org/web/20180718234519/https://aacsla.com/jp/marketplace/evaluating/aacs_technical_overview_040721.pdf"/>
|
||||
/// <see href="https://aacsla.com/wp-content/uploads/2019/02/AACS_Spec_Common_Final_0953.pdf"/>
|
||||
public sealed class SubsetDifference
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -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.
|
||||
/// </summary>
|
||||
/// <see href="http://web.archive.org/web/20180718234519/https://aacsla.com/jp/marketplace/evaluating/aacs_technical_overview_040721.pdf"/>
|
||||
/// <see href="https://aacsla.com/wp-content/uploads/2019/02/AACS_Spec_Common_Final_0953.pdf"/>
|
||||
public sealed class SubsetDifferenceIndexRecord : Record
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -8,14 +8,16 @@ namespace BurnOutSharp.Models.AACS
|
||||
/// fact, more recent than the Media Key Block Extension that is currently
|
||||
/// on the media.
|
||||
/// </summary>
|
||||
/// <see href="http://web.archive.org/web/20180718234519/https://aacsla.com/jp/marketplace/evaluating/aacs_technical_overview_040721.pdf"/>
|
||||
/// <see href="https://aacsla.com/wp-content/uploads/2019/02/AACS_Spec_Common_Final_0953.pdf"/>
|
||||
public sealed class TypeAndVersionRecord : Record
|
||||
{
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public uint MKBType;
|
||||
public MediaKeyBlockType MKBType;
|
||||
|
||||
/// <summary>
|
||||
/// The Version Number is a 32-bit unsigned integer. Each time the
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace BurnOutSharp.Models.AACS
|
||||
/// [AES_128D(vKm, C]msb_64 == 0x0123456789ABCDEF)]
|
||||
/// where Km is the Media Key value.
|
||||
/// </summary>
|
||||
/// <see href="http://web.archive.org/web/20180718234519/https://aacsla.com/jp/marketplace/evaluating/aacs_technical_overview_040721.pdf"/>
|
||||
/// <see href="https://aacsla.com/wp-content/uploads/2019/02/AACS_Spec_Common_Final_0953.pdf"/>
|
||||
public sealed class VerifyMediaKeyRecord : Record
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -370,6 +370,17 @@ namespace BurnOutSharp.Utilities
|
||||
return BitConverter.ToInt16(buffer, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a short from the stream in big-endian format
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a ushort from the stream
|
||||
/// </summary>
|
||||
@@ -380,6 +391,17 @@ namespace BurnOutSharp.Utilities
|
||||
return BitConverter.ToUInt16(buffer, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a ushort from the stream in big-endian format
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read an int from the stream
|
||||
/// </summary>
|
||||
@@ -390,6 +412,17 @@ namespace BurnOutSharp.Utilities
|
||||
return BitConverter.ToInt32(buffer, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read an int from the stream in big-endian format
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a uint from the stream
|
||||
/// </summary>
|
||||
@@ -400,6 +433,17 @@ namespace BurnOutSharp.Utilities
|
||||
return BitConverter.ToUInt32(buffer, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a uint from the stream in big-endian format
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a long from the stream
|
||||
/// </summary>
|
||||
@@ -410,6 +454,17 @@ namespace BurnOutSharp.Utilities
|
||||
return BitConverter.ToInt64(buffer, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a long from the stream in big-endian format
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a ulong from the stream
|
||||
/// </summary>
|
||||
@@ -420,6 +475,17 @@ namespace BurnOutSharp.Utilities
|
||||
return BitConverter.ToUInt64(buffer, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a ulong from the stream in big-endian format
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a Guid from the stream
|
||||
/// </summary>
|
||||
@@ -430,6 +496,17 @@ namespace BurnOutSharp.Utilities
|
||||
return new Guid(buffer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a Guid from the stream in big-endian format
|
||||
/// </summary>
|
||||
public static Guid ReadGuidBE(this Stream stream)
|
||||
{
|
||||
byte[] buffer = new byte[16];
|
||||
stream.Read(buffer, 0, 16);
|
||||
Array.Reverse(buffer);
|
||||
return new Guid(buffer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a null-terminated string from the stream
|
||||
/// </summary>
|
||||
|
||||
Reference in New Issue
Block a user