diff --git a/Aaru.Decryption.csproj b/Aaru.Decryption.csproj
index 72c6796..2eeeef4 100644
--- a/Aaru.Decryption.csproj
+++ b/Aaru.Decryption.csproj
@@ -33,8 +33,8 @@
CS1591;CS1574
-
-
+
+
$(Version)+{chash:8}
@@ -43,9 +43,9 @@
-
-
-
+
+
+
diff --git a/DVD/CSS.cs b/DVD/CSS.cs
index f85ecf5..3a28055 100644
--- a/DVD/CSS.cs
+++ b/DVD/CSS.cs
@@ -44,7 +44,12 @@
// libdvdcss (https://www.videolan.org/developers/libdvdcss.html)
using System;
+using System.Collections.Generic;
using System.Linq;
+using Aaru.CommonTypes;
+using Aaru.CommonTypes.Enums;
+using Aaru.CommonTypes.Interfaces;
+using Aaru.CommonTypes.Structs;
using Aaru.Decoders.DVD;
namespace Aaru.Decryption.DVD;
@@ -544,11 +549,11 @@ public class CSS
}
/// Takes an encrypted key and its crypto and returns the key decrypted.
- ///
+ /// For disc keys, invert is 0x00. For title keys, invert if 0xff.
/// The key used to encrypt the data.
/// The encrypted data.
/// The decrypted data.
- public static void DecryptKey(byte invert, byte[] cryptoKey, byte[] encryptedKey, out byte[] decryptedKey)
+ static void DecryptKey(byte invert, byte[] cryptoKey, byte[] encryptedKey, out byte[] decryptedKey)
{
decryptedKey = new byte[5];
byte[] k = new byte[5];
@@ -590,8 +595,8 @@ public class CSS
decryptedKey[0] = (byte)(k[0] ^ _cssTable1[decryptedKey[0]]);
}
- public static void DecryptTitleKey(byte invert, byte[] cryptoKey, byte[] encryptedKey, out byte[] decryptedKey) =>
- DecryptKey(invert, cryptoKey, encryptedKey, out decryptedKey);
+ public static void DecryptTitleKey(byte[] cryptoKey, byte[] encryptedKey, out byte[] decryptedKey) =>
+ DecryptKey(0xff, cryptoKey, encryptedKey, out decryptedKey);
/// Takes an bytearray of encrypted keys, decrypts them and returns the correctly decrypted key.
/// Encrypted keys to try to decrypt.
@@ -630,65 +635,115 @@ public class CSS
/// Number of sectors in sectorData.
/// Size of one sector.
/// The decrypted sector.
- public static byte[] DecryptSector(byte[] sectorData, byte[] cmiData, byte[] keyData, uint blocks = 1,
+ public static byte[] DecryptSector(byte[] sectorData, byte[] keyData, byte[]? cmiData, uint blocks = 1,
uint blockSize = 2048)
{
- if(cmiData.All(cmi => (cmi & 0x80) >> 7 == 0) ||
- keyData.All(k => k == 0))
+ // None of the sectors are encrypted
+ if((cmiData != null && cmiData.All(static cmi => (cmi & 0x80) >> 7 == 0)) ||
+ keyData.All(static k => k == 0))
return sectorData;
byte[] decryptedBuffer = new byte[sectorData.Length];
- for(uint j = 0; j < blocks; j++)
+ for(uint i = 0; i < blocks; i++)
{
- byte[] currentKey = keyData.Skip((int)(j * 5)).Take(5).ToArray();
- byte[] currentSector = sectorData.Skip((int)(j * blockSize)).Take((int)blockSize).ToArray();
+ byte[] currentKey = keyData.Skip((int)(i * 5)).Take(5).ToArray();
+ byte[] currentSector = sectorData.Skip((int)(i * blockSize)).Take((int)blockSize).ToArray();
- // If the CMI tells use the sector isn't encrypted or
- // if the key is all zeroes or
- // if the MPEG Packetized Elementary Stream scrambling control value tells us the packet is not scrambled
- if((cmiData[j] & 0x80) >> 7 == 0 ||
- currentKey.All(k => k == 0) ||
- (currentSector[20] & 0x30) >> 4 == 0)
+ if(!IsEncrypted(cmiData?[i], currentKey, currentSector))
{
- // Sector is not encrypted
- Array.Copy(currentSector, 0, decryptedBuffer, (int)(j * blockSize), blockSize);
+ Array.Copy(currentSector, 0, decryptedBuffer, (int)(i * blockSize), blockSize);
continue;
}
- uint lfsr1Lo = (uint)(currentKey[0] ^ currentSector[0x54]) | 0x100;
- uint lfsr1Hi = (uint)currentKey[1] ^ currentSector[0x55];
-
- uint lfsr0 = (uint)((currentKey[2] | (currentKey[3] << 8) | (currentKey[4] << 16)) ^
- (sectorData[0x56] | (sectorData[0x57] << 8) | (sectorData[0x58] << 16)));
-
- uint oLfsr1 = lfsr0 & 7;
- lfsr0 = (lfsr0 * 2) + 8 - oLfsr1;
-
- uint combined = 0;
-
- for(uint i = 128; i < blockSize; i++)
- {
- oLfsr1 = (uint)(_cssTable2[lfsr1Hi] ^ _cssTable3[lfsr1Lo]);
- lfsr1Hi = lfsr1Lo >> 1;
- lfsr1Lo = ((lfsr1Lo & 1) << 8) ^ oLfsr1;
- oLfsr1 = _cssTable5[oLfsr1];
- uint oLfsr0 = (((((((lfsr0 >> 3) ^ lfsr0) >> 1) ^ lfsr0) >> 8) ^ lfsr0) >> 5) & 0xff;
- lfsr0 = (lfsr0 >> 8) | (oLfsr0 << 24);
- lfsr0 = (lfsr0 << 8) | oLfsr0;
- oLfsr0 = _cssTable4[oLfsr0];
- combined += oLfsr0 + oLfsr1;
- currentSector[i] = (byte)(_cssTable1[currentSector[i]] ^ (combined & 0xff));
- combined >>= 8;
- }
-
- Array.Copy(currentSector, 0, decryptedBuffer, (int)(j * blockSize), blockSize);
+ Array.Copy(UnscrambleSector(currentKey, currentSector), 0, decryptedBuffer, (int)(i * blockSize),
+ blockSize);
}
return decryptedBuffer;
}
+ ///
+ /// Unscrambles a DVD sector with a title key.
+ ///
+ /// The title key.
+ /// The scrambled sector.
+ /// The unscrambled sector.
+ static byte[] UnscrambleSector(IReadOnlyList key, byte[] sector)
+ {
+ long lfsr1Lo = (key[0] ^ sector[0x54]) | 0x100;
+ long lfsr1Hi = key[1] ^ sector[0x55];
+
+ long lfsr0 = (key[2] | (key[3] << 8) | (key[4] << 16)) ^
+ (sector[0x56] | (sector[0x57] << 8) | (sector[0x58] << 16));
+
+ long oLfsr1 = lfsr0 & 7;
+ lfsr0 = (lfsr0 * 2) + 8 - oLfsr1;
+
+ long combined = 0;
+
+ for(uint i = 0x80; i < 2048; i++)
+ {
+ oLfsr1 = _cssTable2[lfsr1Hi] ^ _cssTable3[lfsr1Lo];
+ lfsr1Hi = lfsr1Lo >> 1;
+ lfsr1Lo = ((lfsr1Lo & 1) << 8) ^ oLfsr1;
+ oLfsr1 = _cssTable5[oLfsr1];
+ long oLfsr0 = (((((((lfsr0 >> 3) ^ lfsr0) >> 1) ^ lfsr0) >> 8) ^ lfsr0) >> 5) & 0xff;
+ lfsr0 = (lfsr0 << 8) | oLfsr0;
+ oLfsr0 = _cssTable4[oLfsr0];
+ combined += oLfsr0 + oLfsr1;
+ sector[i] = (byte)(_cssTable1[sector[i]] ^ (combined & 0xff));
+ combined >>= 8;
+ }
+
+ // Since we unscrambled the sector, we need to set the MPEG Packetized Elementary Stream
+ // scrambling control value to "not scrambled".
+ sector[20] ^= 1 << 4;
+
+ return sector;
+ }
+
+ ///
+ /// Analyzes data to try to figure out if the sector is encrypted, including
+ ///
+ /// - If the packet is not an MPEG packet
+ /// - If the CMI tells us the sector isn't encrypted
+ /// - If the key is all zeroes
+ /// - If the MPEG Packetized Elementary Stream scrambling control value tells us the packet is not scrambled
+ /// - If if the packet is system_header, padding_stream or private_stream2 (cannot be encrypted according to libdvdcss)
+ ///
+ ///
+ /// The Copyright Management Information.
+ /// The title key.
+ /// The sector data.
+ /// True if encrypted
+ static bool IsEncrypted(byte? cmi, byte[]? key, IReadOnlyList sector)
+ {
+ // Only MPEG packets can be encrypted.
+ if(!MPEG.IsMpegPacket(sector))
+ return false;
+
+ // The CMI tells us the sector is not encrypted.
+ if(cmi != null &&
+ (cmi & 0x80) >> 7 == 0)
+ return false;
+
+ // We have the key but it's all zeroes, so sector is unencrypted.
+ if(key != null &&
+ key.All(static k => k == 0))
+ return false;
+
+ // These packet types cannot be encrypted
+ if(sector[17] == (byte)MPEG.Mpeg2StreamId.SystemHeader ||
+ sector[17] == (byte)MPEG.Mpeg2StreamId.PaddingStream ||
+ sector[17] == (byte)MPEG.Mpeg2StreamId.PrivateStream2)
+ return false;
+
+ // MPEG Packetized Elementary Stream scrambling control value
+ return (sector[20] & 0x30) >> 4 == 1;
+ }
+
/// Takes an RPC state from the drive and a CMI from a disc and checks if the regions are compatible.
/// The RegionalPlaybackControlState from drive.
/// The LeadInCopyright from disc.
@@ -708,4 +763,263 @@ public class CSS
((rpc.RegionMask & 0x40) == (cmi.RegionInformation & 0x40) && (rpc.RegionMask & 0x40) != 0x40) ||
((rpc.RegionMask & 0x80) == (cmi.RegionInformation & 0x80) && (rpc.RegionMask & 0x80) != 0x80);
}
+
+ ///
+ /// This tries to find a title key for a range of sectors by doing a brute force pattern search developed by
+ /// Ethan Hawke of DeCSSPlus. CSS encrypted sectors have parts of them that are unencrypted (byte 0x0 - 0x80).
+ /// We try to find a long pattern of repeated bytes just before the encryption starts. If we assume this
+ /// pattern continues into the encrypted part, we can force keys until one of them satisfies this condition.
+ ///
+ /// The sector to analyze.
+ /// The key found.
+ /// true if a key was found.
+ static bool AttackPattern(byte[] sector, out byte[] key)
+ {
+ uint bestPatternLength = 0;
+ uint bestPattern = 0;
+ key = new byte[5];
+
+ for(uint i = 2; i < 0x30; i++)
+ {
+ // Find the number of bytes that repeats in cycles.
+ for(uint j = i + 1; j < 0x80 && sector[0x7F - (j % i)] == sector[0x7F - j]; j++)
+ {
+ if(j <= bestPatternLength)
+ continue;
+
+ bestPatternLength = j;
+ bestPattern = i;
+ }
+ }
+
+ // If we found an adequate pattern.
+ if(bestPattern <= 0 ||
+ bestPatternLength <= 3 ||
+ bestPatternLength / bestPattern < 2)
+ return false;
+
+ int offset = (int)(0x80 - ((bestPatternLength / bestPattern) * bestPattern));
+
+ int result = RecoverTitleKey(0, sector.Skip(0x80).Take(sector.Length - 0x80).ToArray(),
+ sector.Skip(offset).Take((int)(sector.Length - offset)).ToArray(),
+ sector.Skip(0x54).Take(5).ToArray(), out key);
+
+ return result >= 0;
+ }
+
+ ///
+ /// Takes a guessed plain text and a encrypted bytes and tries to recover the title key
+ /// from those. Attack developed by Frank Stevenson.
+ ///
+ /// Start position.
+ /// Buffer with encrypted bytes.
+ /// Buffer with decrypted bytes.
+ /// This sector's seed values.
+ /// The title key.
+ /// Positive values on success.
+ static int RecoverTitleKey(uint start, byte[] encryptedBytes, byte[] decryptedBytes, byte[] sectorSeed,
+ out byte[] key)
+ {
+ byte[] buffer = new byte[10];
+ long oLfsr1;
+ long oLfsr0;
+ long iTry;
+ uint i;
+ int exit = -1;
+ key = new byte[5];
+
+ for(i = 0; i < 10; i++)
+ buffer[i] = (byte)(_cssTable1[encryptedBytes[i]] ^ decryptedBytes[i]);
+
+ for(iTry = start; iTry < 0x10000; iTry++)
+ {
+ long lfsr1Lo = (iTry >> 8) | 0x100;
+ long lfsr1Hi = iTry & 0xff;
+ long lfsr0 = 0;
+ long combined = 0;
+
+ // Iterate cipher 4 times to reconstruct LFSR2
+ for(i = 0; i < 4; i++)
+ {
+ // Advance LFSR1 normally
+ oLfsr1 = _cssTable2[lfsr1Hi] ^ _cssTable3[lfsr1Lo];
+ lfsr1Hi = lfsr1Lo >> 1;
+ lfsr1Lo = ((lfsr1Lo & 1) << 8) ^ oLfsr1;
+ oLfsr1 = _cssTable5[oLfsr1];
+ oLfsr0 = buffer[i];
+
+ if(combined > 0)
+ oLfsr0 = (oLfsr0 + 0xff) & 0x0ff;
+
+ if(oLfsr0 < oLfsr1)
+ oLfsr0 += 0x100;
+
+ oLfsr0 -= oLfsr1;
+ combined += oLfsr0 + oLfsr1;
+ oLfsr0 = _cssTable4[oLfsr0];
+ lfsr0 = (lfsr0 << 8) | oLfsr0;
+ combined >>= 8;
+ }
+
+ long candidate = lfsr0;
+
+ // Iterate 6 more times to validate candidate key
+ for(; i < 10; i++)
+ {
+ oLfsr1 = _cssTable2[lfsr1Hi] ^ _cssTable3[lfsr1Lo];
+ lfsr1Hi = lfsr1Lo >> 1;
+ lfsr1Lo = ((lfsr1Lo & 1) << 8) ^ oLfsr1;
+ oLfsr1 = _cssTable5[oLfsr1];
+ oLfsr0 = (((((((lfsr0 >> 3) ^ lfsr0) >> 1) ^ lfsr0) >> 8) ^ lfsr0) >> 5) & 0xff;
+ lfsr0 = (lfsr0 << 8) | oLfsr0;
+ oLfsr0 = _cssTable4[oLfsr0];
+ combined += oLfsr0 + oLfsr1;
+
+ if((combined & 0xff) != buffer[i])
+ break;
+
+ combined >>= 8;
+ }
+
+ if(i != 10)
+ continue;
+
+ lfsr0 = candidate;
+
+ for(i = 0; i < 4; i++)
+ {
+ lfsr1Lo = lfsr0 & 0xff;
+ lfsr0 = (lfsr0 >> 8);
+
+ for(uint j = 0; j < 256; j++)
+ {
+ lfsr0 = (lfsr0 & 0x1ffff) | (j << 17);
+ oLfsr0 = (((((((lfsr0 >> 3) ^ lfsr0) >> 1) ^ lfsr0) >> 8) ^ lfsr0) >> 5) & 0xff;
+
+ if(oLfsr0 == lfsr1Lo)
+ break;
+ }
+ }
+
+ oLfsr1 = (lfsr0 >> 1) - 4;
+
+ for(combined = 0; combined < 8; combined++)
+ {
+ if(((oLfsr1 + combined) * 2) + 8 - ((oLfsr1 + combined) & 7) != lfsr0)
+ continue;
+
+ key[0] = (byte)(iTry >> 8);
+ key[1] = (byte)(iTry & 0xFF);
+ key[2] = (byte)(((oLfsr1 + combined) >> 0) & 0xFF);
+ key[3] = (byte)(((oLfsr1 + combined) >> 8) & 0xFF);
+ key[4] = (byte)(((oLfsr1 + combined) >> 16) & 0xFF);
+ exit = (int)(iTry + 1);
+ }
+ }
+
+ if(exit < 0)
+ return exit;
+
+ key[0] ^= sectorSeed[0];
+ key[1] ^= sectorSeed[1];
+ key[2] ^= sectorSeed[2];
+ key[3] ^= sectorSeed[3];
+ key[4] ^= sectorSeed[4];
+
+ return exit;
+ }
+
+ ///
+ /// Tries to find a title key by attacking CSS vulnerabilities.
+ ///
+ /// IOpticalMediaImage to find the title key in.
+ /// Sector index to begin search.
+ /// Amount of sectors to search before giving up.
+ /// The title key.
+ static byte[] FindTitleKey(IOpticalMediaImage input, ulong startSector, ulong sectorsToSearch = 20000)
+ {
+ byte[] titleKey = new byte[5];
+
+ for(ulong i = 0; i < sectorsToSearch; i++)
+ {
+ input.ReadSector(startSector + i, out byte[] sector);
+
+ if(!IsEncrypted(null, null, sector))
+ continue;
+
+ if(AttackPattern(sector, out byte[] key))
+ return key;
+ }
+
+ return titleKey;
+ }
+
+ ///
+ /// Generates title keys for all sectors in a track.
+ ///
+ /// IOpticalMediaImage to generate keys for.
+ /// List of Partition to analyze.
+ /// Total number of sectors for track.
+ ///
+ /// A byte array with keys for every sector in the track. One key is 5 bytes.
+ public static byte[] GenerateTitleKeys(IOpticalMediaImage input, List partitions, ulong trackSectors,
+ Type pluginType)
+ {
+ byte[] keys = new byte[trackSectors * 5];
+
+ foreach(Partition partition in partitions)
+ {
+ if(Activator.CreateInstance(pluginType) is not IReadOnlyFilesystem fs)
+ continue;
+
+ if(!HasVideoTsFolder(input, fs, partition))
+ continue;
+
+ if(fs.Mount(input, partition, null, null, null) != ErrorNumber.NoError)
+ continue;
+
+ if(fs.OpenDir("VIDEO_TS", out IDirNode node) == ErrorNumber.NoError)
+ {
+ while(fs.ReadDir(node, out string entry) == ErrorNumber.NoError &&
+ entry is not null)
+ {
+ if(!entry.ToLower().EndsWith(".vob"))
+ continue;
+
+ fs.Stat("VIDEO_TS" + "/" + entry, out FileEntryInfo stat);
+
+ byte[] key = FindTitleKey(input, stat.Inode);
+
+ for(long i = 0; i < stat.Blocks; i++)
+ key.CopyTo(keys, (long)(5 * (stat.Inode + (ulong)i)));
+ }
+
+ fs.CloseDir(node);
+ }
+
+ fs.Unmount();
+ }
+
+ return keys;
+ }
+
+ ///
+ /// DVD video discs always have a VIDEO_TS folder. If it doesn't have one, it's not a DVD video.
+ ///
+ /// IOpticalMediaImage to check for VIDEO_TS folder in.
+ /// IReadOnlyFilesystem to check in.
+ /// Partition to check in.
+ /// true if VIDEO_TS folder was found.
+ static bool HasVideoTsFolder(IOpticalMediaImage input, IReadOnlyFilesystem fs, Partition partition)
+ {
+ ErrorNumber error = fs.Mount(input, partition, null, null, null);
+
+ if(error != ErrorNumber.NoError)
+ return false;
+
+ error = fs.Stat("VIDEO_TS", out FileEntryInfo stat);
+ fs.Unmount();
+
+ return error == ErrorNumber.NoError && stat.Attributes == FileAttributes.Directory;
+ }
}
\ No newline at end of file
diff --git a/DVD/MPEG.cs b/DVD/MPEG.cs
new file mode 100644
index 0000000..70df521
--- /dev/null
+++ b/DVD/MPEG.cs
@@ -0,0 +1,108 @@
+// /***************************************************************************
+// Aaru Data Preservation Suite
+// ----------------------------------------------------------------------------
+//
+// Filename : MPEG.cs
+// Author(s) : Rebecca Wallander
+//
+// --[ Description ] ----------------------------------------------------------
+//
+// Handles MPEG packets functionality.
+//
+// --[ License ] --------------------------------------------------------------
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+// ----------------------------------------------------------------------------
+// Copyright © 2023 Rebecca Wallander
+// ****************************************************************************/
+
+// http://www.mpucoder.com/DVD/vobov.html
+
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.InteropServices;
+
+namespace Aaru.Decryption.DVD;
+
+public class MPEG
+{
+ static byte[] _mpeg2PackHeaderStartCode =
+ {
+ 0x0, 0x0, 0x1
+ };
+
+ public enum Mpeg2StreamId : byte
+ {
+ ProgramEnd = 0xB9, PackHeader = 0xBA, SystemHeader = 0xBB,
+ ProgramStreamMap = 0xBC, PrivateStream1 = 0xBD, PaddingStream = 0xBE,
+ PrivateStream2 = 0xBF, EcmStream = 0xF0, EmmStream = 0xF1,
+ ItuTRecH222_0_or_IsoIec13818_1AnnexA_or_IsoIec13818_6DsmccStream = 0xF2, IsoIec13522Stream = 0xF3,
+ ItuTRecH222_1TypeA = 0xF4, ItuTRecH222_1TypeB = 0xF5, ItuTRecH222_1TypeC = 0xF6,
+ ItuTRecH222_1TypeD = 0xF7, ItuTRecH222_1TypeE = 0xF8, AncillaryStream = 0xF9,
+ Reserved1 = 0xFA, Reserved2 = 0xFB, Reserved3 = 0xFC,
+ Reserved4 = 0xFD, Reserved5 = 0xFE, ProgramStreamDirectory = 0xFF,
+
+ // DVD Video can only hold 8 audio streams
+ MpegAudioStream1 = 0xC0, MpegAudioStream2 = 0xC1, MpegAudioStream3 = 0xC2,
+ MpegAudioStream4 = 0xC3, MpegAudioStream5 = 0xC4, MpegAudioStream6 = 0xC5,
+ MpegAudioStream7 = 0xC6, MpegAudioStream8 = 0xC7, MpegAudioStream9 = 0xC8,
+ MpegAudioStream10 = 0xC9, MpegAudioStream11 = 0xCA, MpegAudioStream12 = 0xCB,
+ MpegAudioStream13 = 0xCC, MpegAudioStream14 = 0xCD, MpegAudioStream15 = 0xCE,
+ MpegAudioStream16 = 0xCF, MpegAudioStream17 = 0xD0, MpegAudioStream18 = 0xD1,
+ MpegAudioStream19 = 0xD2, MpegAudioStream20 = 0xD3, MpegAudioStream21 = 0xD4,
+ MpegAudioStream22 = 0xD5, MpegAudioStream23 = 0xD6, MpegAudioStream24 = 0xD7,
+ MpegAudioStream25 = 0xD8, MpegAudioStream26 = 0xD9, MpegAudioStream27 = 0xDA,
+ MpegAudioStream28 = 0xDB, MpegAudioStream29 = 0xDC, MpegAudioStream30 = 0xDD,
+ MpegAudioStream31 = 0xDE, MpegAudioStream32 = 0xDF,
+
+ // DVD Video can only hold 1 video stream
+ MpegVideStream1 = 0xE0, MpegVideStream2 = 0xE1, MpegVideStream3 = 0xE2,
+ MpegVideStream4 = 0xE3, MpegVideStream5 = 0xE4, MpegVideStream6 = 0xE5,
+ MpegVideStream7 = 0xE6, MpegVideStream8 = 0xE7, MpegVideStream9 = 0xE8,
+ MpegVideStream10 = 0xE9, MpegVideStream11 = 0xEA, MpegVideStream12 = 0xEB,
+ MpegVideStream13 = 0xEC, MpegVideStream14 = 0xED, MpegVideStream15 = 0xEE,
+ MpegVideStream16 = 0xEF
+ }
+
+ public struct MpegHeader
+ {
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
+ public byte[] startCode;
+ public byte packIdentifier;
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
+ public byte[] scrBlock;
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
+ public byte[] programMuxRateBlock;
+ byte packStuffingLengthBlock;
+ }
+
+ public static bool ContainsMpegPackets(byte[] sectorData, uint blocks = 1, uint blockSize = 2048)
+ {
+ for(uint i = 0; i < blocks; i++)
+ if(IsMpegPacket(sectorData.Skip((int)(i * blockSize))))
+ return true;
+
+ return false;
+ }
+
+ public static bool IsMpegPacket(IEnumerable sector) =>
+ sector.Take(3).ToArray().SequenceEqual(_mpeg2PackHeaderStartCode);
+}
\ No newline at end of file