Files

440 lines
16 KiB
C#
Raw Permalink Normal View History

using System.Collections.Generic;
2023-09-08 16:55:35 -04:00
using System.IO;
using System.Text;
2025-09-26 13:06:18 -04:00
using SabreTools.Data.Models.AACS;
2024-04-17 11:52:22 -04:00
using SabreTools.IO.Extensions;
2026-03-24 19:17:25 -04:00
using SabreTools.Numerics.Extensions;
2023-09-08 16:55:35 -04:00
2026-01-27 12:03:01 -05:00
#pragma warning disable IDE0017 // Simplify object initialization
2025-09-26 14:57:20 -04:00
namespace SabreTools.Serialization.Readers
2023-09-08 16:55:35 -04:00
{
public class AACS : BaseBinaryReader<MediaKeyBlock>
2023-09-08 16:55:35 -04:00
{
/// <inheritdoc/>
public override MediaKeyBlock? Deserialize(Stream? data)
{
// If the data is invalid
2026-01-25 14:30:18 -05:00
if (data is null || !data.CanRead)
return null;
try
{
#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);
2026-01-25 14:30:18 -05:00
if (record is null)
continue;
// 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
2024-11-29 20:48:50 -05:00
if (!data.AlignToBoundary(4))
break;
}
#endregion
2024-11-28 23:06:09 -05:00
// Set the records
if (records.Count > 0)
2024-12-16 23:08:45 -05:00
return new MediaKeyBlock { Records = [.. records] };
2024-11-28 23:06:09 -05:00
return null;
}
catch
{
// Ignore the actual error
return null;
}
}
/// <summary>
2024-12-16 23:08:45 -05:00
/// Parse a Stream into a Record
/// </summary>
/// <param name="data">Stream to parse</param>
2024-12-16 23:08:45 -05:00
/// <returns>Filled Record on success, null on error</returns>
private static Record? ParseRecord(Stream data)
{
// The first 4 bytes are the type and length
2025-11-14 14:06:43 -05:00
RecordType type = (RecordType)data.PeekByteValue();
// Create a record based on the type
2025-09-22 20:07:18 -04:00
return type switch
{
2024-11-19 12:40:32 -05:00
// Known record types
2025-09-22 20:07:18 -04:00
RecordType.EndOfMediaKeyBlock => ParseEndOfMediaKeyBlockRecord(data),
RecordType.ExplicitSubsetDifference => ParseExplicitSubsetDifferenceRecord(data),
RecordType.MediaKeyData => ParseMediaKeyDataRecord(data),
RecordType.SubsetDifferenceIndex => ParseSubsetDifferenceIndexRecord(data),
RecordType.TypeAndVersion => ParseTypeAndVersionRecord(data),
RecordType.DriveRevocationList => ParseDriveRevocationListRecord(data),
RecordType.HostRevocationList => ParseHostRevocationListRecord(data),
RecordType.VerifyMediaKey => ParseVerifyMediaKeyRecord(data),
RecordType.Copyright => ParseCopyrightRecord(data),
2026-01-25 16:15:05 -05:00
// Unimplemented
RecordType.MediaKeyVariantData => ParseGenericRecord(data),
RecordType.Unknown0x28_AACS2 => ParseGenericRecord(data),
RecordType.DriveRevocationList_AACS2 => ParseGenericRecord(data),
RecordType.HostRevocationList_AACS2 => ParseGenericRecord(data),
RecordType.VerifyMediaKey_AACS2 => ParseGenericRecord(data),
RecordType.EmptyRecord0xF8_AACS2 => ParseGenericRecord(data),
2024-11-19 12:40:32 -05:00
// Unknown record type
2025-09-22 20:07:18 -04:00
_ => ParseGenericRecord(data),
};
}
/// <summary>
2024-12-16 23:08:45 -05:00
/// Parse a Stream into a CopyrightRecord
/// </summary>
/// <param name="data">Stream to parse</param>
2024-12-16 23:08:45 -05:00
/// <returns>Filled CopyrightRecord on success, null on error</returns>
public static CopyrightRecord ParseCopyrightRecord(Stream data)
{
2024-12-16 23:08:45 -05:00
var obj = new CopyrightRecord();
2024-12-16 23:08:45 -05:00
obj.RecordType = (RecordType)data.ReadByteValue();
obj.RecordLength = data.ReadUInt24LittleEndian();
2026-03-24 19:17:25 -04:00
if ((uint)obj.RecordLength > 4)
2024-12-16 23:08:45 -05:00
{
2026-03-24 19:17:25 -04:00
byte[] copyright = data.ReadBytes((int)((uint)obj.RecordLength - 4));
2024-12-16 23:08:45 -05:00
obj.Copyright = Encoding.ASCII.GetString(copyright).TrimEnd('\0');
}
2024-12-16 23:08:45 -05:00
return obj;
}
/// <summary>
2024-12-16 23:08:45 -05:00
/// Parse a Stream into a DriveRevocationListEntry
/// </summary>
/// <param name="data">Stream to parse</param>
2024-12-16 23:08:45 -05:00
/// <returns>Filled DriveRevocationListEntry on success, null on error</returns>
public static DriveRevocationListEntry ParseDriveRevocationListEntry(Stream data)
{
2024-12-16 23:08:45 -05:00
var obj = new DriveRevocationListEntry();
2024-12-16 23:08:45 -05:00
obj.Range = data.ReadUInt16BigEndian();
obj.DriveID = data.ReadBytes(6);
2024-12-16 23:08:45 -05:00
return obj;
}
2024-12-16 23:08:45 -05:00
/// <summary>
/// Parse a Stream into a DriveRevocationListRecord
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled DriveRevocationListRecord on success, null on error</returns>
public static DriveRevocationListRecord ParseDriveRevocationListRecord(Stream data)
{
// Cache the current offset
2024-12-16 23:08:45 -05:00
long initialOffset = data.Position;
2024-12-16 23:08:45 -05:00
var obj = new DriveRevocationListRecord();
2024-12-16 23:08:45 -05:00
obj.RecordType = (RecordType)data.ReadByteValue();
obj.RecordLength = data.ReadUInt24LittleEndian();
2024-12-16 23:08:45 -05:00
obj.TotalNumberOfEntries = data.ReadUInt32BigEndian();
2024-12-16 23:08:45 -05:00
// Try to parse the signature blocks
var blocks = new List<DriveRevocationSignatureBlock>();
uint entryCount = 0;
2026-03-24 19:17:25 -04:00
while (entryCount < obj.TotalNumberOfEntries && data.Position < initialOffset + (uint)obj.RecordLength)
2024-12-16 23:08:45 -05:00
{
var block = ParseDriveRevocationSignatureBlock(data);
entryCount += block.NumberOfEntries;
blocks.Add(block);
2024-12-16 23:08:45 -05:00
// If we have an empty block
if (block.NumberOfEntries == 0)
break;
}
2024-12-16 23:08:45 -05:00
// Set the signature blocks
obj.SignatureBlocks = [.. blocks];
// If there's any data left, discard it
2026-03-24 19:17:25 -04:00
if (data.Position < initialOffset + (uint)obj.RecordLength)
_ = data.ReadBytes((int)(initialOffset + (uint)obj.RecordLength - data.Position));
2024-12-16 23:08:45 -05:00
return obj;
}
/// <summary>
2024-12-16 23:08:45 -05:00
/// Parse a Stream into a DriveRevocationSignatureBlock
/// </summary>
/// <param name="data">Stream to parse</param>
2024-12-16 23:08:45 -05:00
/// <returns>Filled DriveRevocationSignatureBlock on success, null on error</returns>
public static DriveRevocationSignatureBlock ParseDriveRevocationSignatureBlock(Stream data)
{
2024-12-16 23:08:45 -05:00
var obj = new DriveRevocationSignatureBlock();
2024-12-16 23:08:45 -05:00
obj.NumberOfEntries = data.ReadUInt32BigEndian();
obj.EntryFields = new DriveRevocationListEntry[obj.NumberOfEntries];
for (int i = 0; i < obj.EntryFields.Length; i++)
{
2024-12-16 23:08:45 -05:00
obj.EntryFields[i] = ParseDriveRevocationListEntry(data);
}
2024-12-16 23:08:45 -05:00
return obj;
}
/// <summary>
2024-12-16 23:08:45 -05:00
/// Parse a Stream into a EndOfMediaKeyBlockRecord
/// </summary>
/// <param name="data">Stream to parse</param>
2024-12-16 23:08:45 -05:00
/// <returns>Filled EndOfMediaKeyBlockRecord on success, null on error</returns>
public static EndOfMediaKeyBlockRecord ParseEndOfMediaKeyBlockRecord(Stream data)
{
2024-12-16 23:08:45 -05:00
var obj = new EndOfMediaKeyBlockRecord();
2024-12-16 23:08:45 -05:00
obj.RecordType = (RecordType)data.ReadByteValue();
obj.RecordLength = data.ReadUInt24LittleEndian();
2026-03-24 19:17:25 -04:00
if ((uint)obj.RecordLength > 4)
obj.SignatureData = data.ReadBytes((int)((uint)obj.RecordLength - 4));
2024-12-16 23:08:45 -05:00
return obj;
}
2024-12-16 23:08:45 -05:00
/// <summary>
/// Parse a Stream into a ExplicitSubsetDifferenceRecord
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled ExplicitSubsetDifferenceRecord on success, null on error</returns>
public static ExplicitSubsetDifferenceRecord ParseExplicitSubsetDifferenceRecord(Stream data)
{
// Cache the current offset
2024-12-16 23:08:45 -05:00
long initialOffset = data.Position;
2024-12-16 23:08:45 -05:00
var obj = new ExplicitSubsetDifferenceRecord();
2024-12-16 23:08:45 -05:00
obj.RecordType = (RecordType)data.ReadByteValue();
obj.RecordLength = data.ReadUInt24LittleEndian();
2024-12-16 23:08:45 -05:00
// Try to parse the subset differences
var subsetDifferences = new List<SubsetDifference>();
2026-03-24 19:17:25 -04:00
while (data.Position < initialOffset + (uint)obj.RecordLength - 5)
{
2024-12-16 23:08:45 -05:00
var subsetDifference = ParseSubsetDifference(data);
subsetDifferences.Add(subsetDifference);
}
2024-12-16 23:08:45 -05:00
// Set the subset differences
obj.SubsetDifferences = [.. subsetDifferences];
2024-12-16 23:08:45 -05:00
// If there's any data left, discard it
2026-03-24 19:17:25 -04:00
if (data.Position < initialOffset + (uint)obj.RecordLength)
_ = data.ReadBytes((int)(initialOffset + (uint)obj.RecordLength - data.Position));
2024-12-16 23:08:45 -05:00
return obj;
}
2025-09-22 20:07:18 -04:00
/// <summary>
/// Parse a Stream into a GenericRecord
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled GenericRecord on success, null on error</returns>
public static GenericRecord ParseGenericRecord(Stream data)
{
var obj = new GenericRecord();
obj.RecordType = (RecordType)data.ReadByteValue();
obj.RecordLength = data.ReadUInt24LittleEndian();
obj.Data = data.ReadBytes(0x10);
return obj;
}
/// <summary>
2024-12-16 23:08:45 -05:00
/// Parse a Stream into a HostRevocationListEntry
/// </summary>
/// <param name="data">Stream to parse</param>
2024-12-16 23:08:45 -05:00
/// <returns>Filled HostRevocationListEntry on success, null on error</returns>
public static HostRevocationListEntry ParseHostRevocationListEntry(Stream data)
{
2024-12-16 23:08:45 -05:00
var obj = new HostRevocationListEntry();
2024-12-16 23:08:45 -05:00
obj.Range = data.ReadUInt16BigEndian();
obj.HostID = data.ReadBytes(6);
2024-12-16 23:08:45 -05:00
return obj;
}
/// <summary>
2024-12-16 23:08:45 -05:00
/// Parse a Stream into a HostRevocationListRecord
/// </summary>
/// <param name="data">Stream to parse</param>
2024-12-16 23:08:45 -05:00
/// <returns>Filled HostRevocationListRecord on success, null on error</returns>
public static HostRevocationListRecord ParseHostRevocationListRecord(Stream data)
{
// Cache the current offset
2024-12-16 23:08:45 -05:00
long initialOffset = data.Position;
2024-12-16 23:08:45 -05:00
var obj = new HostRevocationListRecord();
2024-12-16 23:08:45 -05:00
obj.RecordType = (RecordType)data.ReadByteValue();
obj.RecordLength = data.ReadUInt24LittleEndian();
2024-12-16 23:08:45 -05:00
obj.TotalNumberOfEntries = data.ReadUInt32BigEndian();
// Try to parse the signature blocks
2024-12-16 23:08:45 -05:00
var blocks = new List<HostRevocationSignatureBlock>();
2026-03-24 19:17:25 -04:00
for (uint entryCount = 0; entryCount < obj.TotalNumberOfEntries && data.Position < initialOffset + (uint)obj.RecordLength;)
{
2024-12-16 23:08:45 -05:00
var block = ParseHostRevocationSignatureBlock(data);
entryCount += block.NumberOfEntries;
blocks.Add(block);
// If we have an empty block
if (block.NumberOfEntries == 0)
break;
}
// Set the signature blocks
2024-12-16 23:08:45 -05:00
obj.SignatureBlocks = [.. blocks];
// If there's any data left, discard it
2026-03-24 19:17:25 -04:00
if (data.Position < initialOffset + (uint)obj.RecordLength)
_ = data.ReadBytes((int)(initialOffset + (uint)obj.RecordLength - data.Position));
2024-12-16 23:08:45 -05:00
return obj;
}
/// <summary>
2024-12-16 23:08:45 -05:00
/// Parse a Stream into a HostRevocationSignatureBlock
/// </summary>
/// <param name="data">Stream to parse</param>
2024-12-16 23:08:45 -05:00
/// <returns>Filled HostRevocationSignatureBlock on success, null on error</returns>
public static HostRevocationSignatureBlock ParseHostRevocationSignatureBlock(Stream data)
{
2024-12-16 23:08:45 -05:00
var obj = new HostRevocationSignatureBlock();
2024-12-16 23:08:45 -05:00
obj.NumberOfEntries = data.ReadUInt32BigEndian();
obj.EntryFields = new HostRevocationListEntry[obj.NumberOfEntries];
for (int i = 0; i < obj.EntryFields.Length; i++)
{
obj.EntryFields[i] = ParseHostRevocationListEntry(data);
}
2024-12-16 23:08:45 -05:00
return obj;
}
2024-12-16 23:08:45 -05:00
/// <summary>
/// Parse a Stream into a MediaKeyDataRecord
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled MediaKeyDataRecord on success, null on error</returns>
public static MediaKeyDataRecord ParseMediaKeyDataRecord(Stream data)
{
// Cache the current offset
2024-12-16 23:08:45 -05:00
long initialOffset = data.Position;
2024-12-16 23:08:45 -05:00
var obj = new MediaKeyDataRecord();
2024-12-16 23:08:45 -05:00
obj.RecordType = (RecordType)data.ReadByteValue();
obj.RecordLength = data.ReadUInt24LittleEndian();
2024-12-16 23:08:45 -05:00
// Try to parse the media keys
var mediaKeys = new List<byte[]>();
2026-03-24 19:17:25 -04:00
while (data.Position < initialOffset + (uint)obj.RecordLength)
{
2024-12-16 23:08:45 -05:00
byte[] mediaKey = data.ReadBytes(0x10);
mediaKeys.Add(mediaKey);
}
2024-12-16 23:08:45 -05:00
// Set the media keys
obj.MediaKeyData = [.. mediaKeys];
2024-12-16 23:08:45 -05:00
return obj;
}
2024-12-16 23:08:45 -05:00
/// <summary>
/// Parse a Stream into a SubsetDifference
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled SubsetDifference on success, null on error</returns>
public static SubsetDifference ParseSubsetDifference(Stream data)
{
var obj = new SubsetDifference();
2024-12-16 23:08:45 -05:00
obj.Mask = data.ReadByteValue();
obj.Number = data.ReadUInt32BigEndian();
2024-12-16 23:08:45 -05:00
return obj;
}
2024-12-16 23:08:45 -05:00
/// <summary>
/// Parse a Stream into a SubsetDifferenceIndexRecord
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled SubsetDifferenceIndexRecord on success, null on error</returns>
public static SubsetDifferenceIndexRecord ParseSubsetDifferenceIndexRecord(Stream data)
{
// Cache the current offset
long initialOffset = data.Position;
2024-12-16 23:08:45 -05:00
var obj = new SubsetDifferenceIndexRecord();
obj.RecordType = (RecordType)data.ReadByteValue();
obj.RecordLength = data.ReadUInt24LittleEndian();
2024-12-16 23:08:45 -05:00
obj.Span = data.ReadUInt32BigEndian();
2024-12-16 23:08:45 -05:00
// Try to parse the offsets
var offsets = new List<uint>();
2026-03-24 19:17:25 -04:00
while (data.Position < initialOffset + (uint)obj.RecordLength)
2024-12-16 23:08:45 -05:00
{
uint offset = data.ReadUInt32BigEndian();
offsets.Add(offset);
}
// Set the offsets
obj.Offsets = [.. offsets];
return obj;
}
/// <summary>
2024-12-16 23:08:45 -05:00
/// Parse a Stream into a TypeAndVersionRecord
/// </summary>
/// <param name="data">Stream to parse</param>
2024-12-16 23:08:45 -05:00
/// <returns>Filled TypeAndVersionRecord on success, null on error</returns>
public static TypeAndVersionRecord ParseTypeAndVersionRecord(Stream data)
{
2024-12-16 23:08:45 -05:00
var obj = new TypeAndVersionRecord();
2024-12-16 23:08:45 -05:00
obj.RecordType = (RecordType)data.ReadByteValue();
obj.RecordLength = data.ReadUInt24LittleEndian();
2024-12-16 23:08:45 -05:00
obj.MediaKeyBlockType = (MediaKeyBlockType)data.ReadUInt32BigEndian();
obj.VersionNumber = data.ReadUInt32BigEndian();
2024-12-16 23:08:45 -05:00
return obj;
}
/// <summary>
2024-12-16 23:08:45 -05:00
/// Parse a Stream into a VerifyMediaKeyRecord
/// </summary>
/// <param name="data">Stream to parse</param>
2024-12-16 23:08:45 -05:00
/// <returns>Filled VerifyMediaKeyRecord on success, null on error</returns>
public static VerifyMediaKeyRecord ParseVerifyMediaKeyRecord(Stream data)
{
2024-12-16 23:08:45 -05:00
var obj = new VerifyMediaKeyRecord();
2024-12-16 23:08:45 -05:00
obj.RecordType = (RecordType)data.ReadByteValue();
obj.RecordLength = data.ReadUInt24LittleEndian();
2024-12-16 23:08:45 -05:00
obj.CiphertextValue = data.ReadBytes(0x10);
2024-12-16 23:08:45 -05:00
return obj;
}
2023-09-08 16:55:35 -04:00
}
}