diff --git a/Bytes/AACS.Deserializer.cs b/Bytes/AACS.Deserializer.cs new file mode 100644 index 00000000..6cee7caf --- /dev/null +++ b/Bytes/AACS.Deserializer.cs @@ -0,0 +1,28 @@ +using System.IO; +using SabreTools.Models.AACS; + +namespace SabreTools.Serialization.Bytes +{ + public partial class AACS : IByteSerializer + { + /// +#if NET48 + public MediaKeyBlock Deserialize(byte[] data, int offset) +#else + public MediaKeyBlock? Deserialize(byte[]? data, int offset) +#endif + { + // 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 new Streams.AACS().Deserialize(dataStream); + } + } +} \ No newline at end of file diff --git a/Streams/AACS.Deserializer.cs b/Streams/AACS.Deserializer.cs new file mode 100644 index 00000000..e92efc62 --- /dev/null +++ b/Streams/AACS.Deserializer.cs @@ -0,0 +1,457 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using SabreTools.IO; +using SabreTools.Models.AACS; + +namespace SabreTools.Serialization.Streams +{ + public partial class AACS : IStreamSerializer + { + /// + /// Parse a Stream into an AACS media key block + /// + /// Stream to parse + /// Filled cmedia key block on success, null on error +#if NET48 + public MediaKeyBlock Deserialize(Stream data) +#else + public MediaKeyBlock? Deserialize(Stream? data) +#endif + { + // 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 +#if NET48 + private static Record ParseRecord(Stream data) +#else + private static Record? ParseRecord(Stream data) +#endif + { + // 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 +#if NET48 + private static EndOfMediaKeyBlockRecord ParseEndOfMediaKeyBlockRecord(Stream data, RecordType type, uint length) +#else + private static EndOfMediaKeyBlockRecord? ParseEndOfMediaKeyBlockRecord(Stream data, RecordType type, uint length) +#endif + { + // 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 +#if NET48 + private static ExplicitSubsetDifferenceRecord ParseExplicitSubsetDifferenceRecord(Stream data, RecordType type, uint length) +#else + private static ExplicitSubsetDifferenceRecord? ParseExplicitSubsetDifferenceRecord(Stream data, RecordType type, uint length) +#endif + { + // Verify we're calling the right parser + if (type != RecordType.ExplicitSubsetDifference) + return null; + + // TODO: Use marshalling here instead of building + var record = new ExplicitSubsetDifferenceRecord(); + + record.RecordType = type; + record.RecordLength = length; + + // 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.ReadUInt32BigEndian(); + + subsetDifferences.Add(subsetDifference); + } + + // Set the subset differences + record.SubsetDifferences = subsetDifferences.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 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(); + + record.RecordType = type; + record.RecordLength = length; + + // 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(); + + record.RecordType = type; + record.RecordLength = length; + + // Cache the current offset + long initialOffset = data.Position - 4; + + record.Span = data.ReadUInt32BigEndian(); + + // Create the offset list + var offsets = new List(); + + // Try to parse the offsets + while (data.Position < initialOffset + length) + { + uint offset = data.ReadUInt32BigEndian(); + 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.MediaKeyBlockType = (MediaKeyBlockType)data.ReadUInt32BigEndian(); + record.VersionNumber = data.ReadUInt32BigEndian(); + + 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(); + + record.RecordType = type; + record.RecordLength = length; + + // Cache the current offset + long initialOffset = data.Position - 4; + + record.TotalNumberOfEntries = data.ReadUInt32BigEndian(); + + // Create the signature blocks list + var blocks = new List(); + + // Try to parse the signature blocks + int entryCount = 0; + while (entryCount < record.TotalNumberOfEntries && data.Position < initialOffset + length) + { + var block = new DriveRevocationSignatureBlock(); + + block.NumberOfEntries = data.ReadUInt32BigEndian(); + block.EntryFields = new DriveRevocationListEntry[block.NumberOfEntries]; + for (int i = 0; i < block.EntryFields.Length; i++) + { + var entry = new DriveRevocationListEntry(); + + entry.Range = data.ReadUInt16BigEndian(); + entry.DriveID = data.ReadBytes(6); + + block.EntryFields[i] = entry; + entryCount++; + } + + 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(); + + record.RecordType = type; + record.RecordLength = length; + + // Cache the current offset + long initialOffset = data.Position - 4; + + record.TotalNumberOfEntries = data.ReadUInt32BigEndian(); + + // Create the signature blocks list + var blocks = new List(); + + // Try to parse the signature blocks + int entryCount = 0; + while (entryCount < record.TotalNumberOfEntries && data.Position < initialOffset + length) + { + var block = new HostRevocationSignatureBlock(); + + block.NumberOfEntries = data.ReadUInt32BigEndian(); + block.EntryFields = new HostRevocationListEntry[block.NumberOfEntries]; + for (int i = 0; i < block.EntryFields.Length; i++) + { + var entry = new HostRevocationListEntry(); + + entry.Range = data.ReadUInt16BigEndian(); + entry.HostID = data.ReadBytes(6); + + block.EntryFields[i] = entry; + entryCount++; + } + + 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; + } + } +} \ No newline at end of file