using System.Collections.Generic; using System.IO; using System.Text; using SabreTools.Data.Models.AACS; using SabreTools.IO.Extensions; using SabreTools.Numerics.Extensions; #pragma warning disable IDE0017 // Simplify object initialization namespace SabreTools.Serialization.Readers { public class AACS : BaseBinaryReader { /// public override MediaKeyBlock? Deserialize(Stream? data) { // If the data is invalid if (data is null || !data.CanRead) return null; try { #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 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 if (!data.AlignToBoundary(4)) break; } #endregion // Set the records if (records.Count > 0) return new MediaKeyBlock { Records = [.. records] }; return null; } catch { // Ignore the actual error return null; } } /// /// Parse a Stream into a Record /// /// Stream to parse /// Filled Record on success, null on error private static Record? ParseRecord(Stream data) { // The first 4 bytes are the type and length RecordType type = (RecordType)data.PeekByteValue(); // Create a record based on the type return type switch { // Known record types 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), // 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), // Unknown record type _ => ParseGenericRecord(data), }; } /// /// Parse a Stream into a CopyrightRecord /// /// Stream to parse /// Filled CopyrightRecord on success, null on error public static CopyrightRecord ParseCopyrightRecord(Stream data) { var obj = new CopyrightRecord(); obj.RecordType = (RecordType)data.ReadByteValue(); obj.RecordLength = data.ReadUInt24LittleEndian(); if ((uint)obj.RecordLength > 4) { byte[] copyright = data.ReadBytes((int)((uint)obj.RecordLength - 4)); obj.Copyright = Encoding.ASCII.GetString(copyright).TrimEnd('\0'); } return obj; } /// /// Parse a Stream into a DriveRevocationListEntry /// /// Stream to parse /// Filled DriveRevocationListEntry on success, null on error public static DriveRevocationListEntry ParseDriveRevocationListEntry(Stream data) { var obj = new DriveRevocationListEntry(); obj.Range = data.ReadUInt16BigEndian(); obj.DriveID = data.ReadBytes(6); return obj; } /// /// Parse a Stream into a DriveRevocationListRecord /// /// Stream to parse /// Filled DriveRevocationListRecord on success, null on error public static DriveRevocationListRecord ParseDriveRevocationListRecord(Stream data) { // Cache the current offset long initialOffset = data.Position; var obj = new DriveRevocationListRecord(); obj.RecordType = (RecordType)data.ReadByteValue(); obj.RecordLength = data.ReadUInt24LittleEndian(); obj.TotalNumberOfEntries = data.ReadUInt32BigEndian(); // Try to parse the signature blocks var blocks = new List(); uint entryCount = 0; while (entryCount < obj.TotalNumberOfEntries && data.Position < initialOffset + (uint)obj.RecordLength) { var block = ParseDriveRevocationSignatureBlock(data); entryCount += block.NumberOfEntries; blocks.Add(block); // If we have an empty block if (block.NumberOfEntries == 0) break; } // Set the signature blocks obj.SignatureBlocks = [.. blocks]; // If there's any data left, discard it if (data.Position < initialOffset + (uint)obj.RecordLength) _ = data.ReadBytes((int)(initialOffset + (uint)obj.RecordLength - data.Position)); return obj; } /// /// Parse a Stream into a DriveRevocationSignatureBlock /// /// Stream to parse /// Filled DriveRevocationSignatureBlock on success, null on error public static DriveRevocationSignatureBlock ParseDriveRevocationSignatureBlock(Stream data) { var obj = new DriveRevocationSignatureBlock(); obj.NumberOfEntries = data.ReadUInt32BigEndian(); obj.EntryFields = new DriveRevocationListEntry[obj.NumberOfEntries]; for (int i = 0; i < obj.EntryFields.Length; i++) { obj.EntryFields[i] = ParseDriveRevocationListEntry(data); } return obj; } /// /// Parse a Stream into a EndOfMediaKeyBlockRecord /// /// Stream to parse /// Filled EndOfMediaKeyBlockRecord on success, null on error public static EndOfMediaKeyBlockRecord ParseEndOfMediaKeyBlockRecord(Stream data) { var obj = new EndOfMediaKeyBlockRecord(); obj.RecordType = (RecordType)data.ReadByteValue(); obj.RecordLength = data.ReadUInt24LittleEndian(); if ((uint)obj.RecordLength > 4) obj.SignatureData = data.ReadBytes((int)((uint)obj.RecordLength - 4)); return obj; } /// /// Parse a Stream into a ExplicitSubsetDifferenceRecord /// /// Stream to parse /// Filled ExplicitSubsetDifferenceRecord on success, null on error public static ExplicitSubsetDifferenceRecord ParseExplicitSubsetDifferenceRecord(Stream data) { // Cache the current offset long initialOffset = data.Position; var obj = new ExplicitSubsetDifferenceRecord(); obj.RecordType = (RecordType)data.ReadByteValue(); obj.RecordLength = data.ReadUInt24LittleEndian(); // Try to parse the subset differences var subsetDifferences = new List(); while (data.Position < initialOffset + (uint)obj.RecordLength - 5) { var subsetDifference = ParseSubsetDifference(data); subsetDifferences.Add(subsetDifference); } // Set the subset differences obj.SubsetDifferences = [.. subsetDifferences]; // If there's any data left, discard it if (data.Position < initialOffset + (uint)obj.RecordLength) _ = data.ReadBytes((int)(initialOffset + (uint)obj.RecordLength - data.Position)); return obj; } /// /// Parse a Stream into a GenericRecord /// /// Stream to parse /// Filled GenericRecord on success, null on error 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; } /// /// Parse a Stream into a HostRevocationListEntry /// /// Stream to parse /// Filled HostRevocationListEntry on success, null on error public static HostRevocationListEntry ParseHostRevocationListEntry(Stream data) { var obj = new HostRevocationListEntry(); obj.Range = data.ReadUInt16BigEndian(); obj.HostID = data.ReadBytes(6); return obj; } /// /// Parse a Stream into a HostRevocationListRecord /// /// Stream to parse /// Filled HostRevocationListRecord on success, null on error public static HostRevocationListRecord ParseHostRevocationListRecord(Stream data) { // Cache the current offset long initialOffset = data.Position; var obj = new HostRevocationListRecord(); obj.RecordType = (RecordType)data.ReadByteValue(); obj.RecordLength = data.ReadUInt24LittleEndian(); obj.TotalNumberOfEntries = data.ReadUInt32BigEndian(); // Try to parse the signature blocks var blocks = new List(); for (uint entryCount = 0; entryCount < obj.TotalNumberOfEntries && data.Position < initialOffset + (uint)obj.RecordLength;) { 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 obj.SignatureBlocks = [.. blocks]; // If there's any data left, discard it if (data.Position < initialOffset + (uint)obj.RecordLength) _ = data.ReadBytes((int)(initialOffset + (uint)obj.RecordLength - data.Position)); return obj; } /// /// Parse a Stream into a HostRevocationSignatureBlock /// /// Stream to parse /// Filled HostRevocationSignatureBlock on success, null on error public static HostRevocationSignatureBlock ParseHostRevocationSignatureBlock(Stream data) { var obj = new HostRevocationSignatureBlock(); obj.NumberOfEntries = data.ReadUInt32BigEndian(); obj.EntryFields = new HostRevocationListEntry[obj.NumberOfEntries]; for (int i = 0; i < obj.EntryFields.Length; i++) { obj.EntryFields[i] = ParseHostRevocationListEntry(data); } return obj; } /// /// Parse a Stream into a MediaKeyDataRecord /// /// Stream to parse /// Filled MediaKeyDataRecord on success, null on error public static MediaKeyDataRecord ParseMediaKeyDataRecord(Stream data) { // Cache the current offset long initialOffset = data.Position; var obj = new MediaKeyDataRecord(); obj.RecordType = (RecordType)data.ReadByteValue(); obj.RecordLength = data.ReadUInt24LittleEndian(); // Try to parse the media keys var mediaKeys = new List(); while (data.Position < initialOffset + (uint)obj.RecordLength) { byte[] mediaKey = data.ReadBytes(0x10); mediaKeys.Add(mediaKey); } // Set the media keys obj.MediaKeyData = [.. mediaKeys]; return obj; } /// /// Parse a Stream into a SubsetDifference /// /// Stream to parse /// Filled SubsetDifference on success, null on error public static SubsetDifference ParseSubsetDifference(Stream data) { var obj = new SubsetDifference(); obj.Mask = data.ReadByteValue(); obj.Number = data.ReadUInt32BigEndian(); return obj; } /// /// Parse a Stream into a SubsetDifferenceIndexRecord /// /// Stream to parse /// Filled SubsetDifferenceIndexRecord on success, null on error public static SubsetDifferenceIndexRecord ParseSubsetDifferenceIndexRecord(Stream data) { // Cache the current offset long initialOffset = data.Position; var obj = new SubsetDifferenceIndexRecord(); obj.RecordType = (RecordType)data.ReadByteValue(); obj.RecordLength = data.ReadUInt24LittleEndian(); obj.Span = data.ReadUInt32BigEndian(); // Try to parse the offsets var offsets = new List(); while (data.Position < initialOffset + (uint)obj.RecordLength) { uint offset = data.ReadUInt32BigEndian(); offsets.Add(offset); } // Set the offsets obj.Offsets = [.. offsets]; return obj; } /// /// Parse a Stream into a TypeAndVersionRecord /// /// Stream to parse /// Filled TypeAndVersionRecord on success, null on error public static TypeAndVersionRecord ParseTypeAndVersionRecord(Stream data) { var obj = new TypeAndVersionRecord(); obj.RecordType = (RecordType)data.ReadByteValue(); obj.RecordLength = data.ReadUInt24LittleEndian(); obj.MediaKeyBlockType = (MediaKeyBlockType)data.ReadUInt32BigEndian(); obj.VersionNumber = data.ReadUInt32BigEndian(); return obj; } /// /// Parse a Stream into a VerifyMediaKeyRecord /// /// Stream to parse /// Filled VerifyMediaKeyRecord on success, null on error public static VerifyMediaKeyRecord ParseVerifyMediaKeyRecord(Stream data) { var obj = new VerifyMediaKeyRecord(); obj.RecordType = (RecordType)data.ReadByteValue(); obj.RecordLength = data.ReadUInt24LittleEndian(); obj.CiphertextValue = data.ReadBytes(0x10); return obj; } } }