mirror of
https://github.com/aaru-dps/Aaru.git
synced 2026-07-08 18:16:24 +00:00
[convert] Use the new CSS/AACS pipelines
This commit is contained in:
@@ -6,15 +6,15 @@ using Aaru.CommonTypes.Enums;
|
||||
using Aaru.CommonTypes.Interfaces;
|
||||
using Aaru.CommonTypes.Structs;
|
||||
using Aaru.Core.Media;
|
||||
using Aaru.Decryption.DVD;
|
||||
using Aaru.Devices;
|
||||
using Aaru.Localization;
|
||||
using Aaru.Logging;
|
||||
using MediaType = Aaru.CommonTypes.MediaType;
|
||||
|
||||
namespace Aaru.Core.Image;
|
||||
|
||||
public partial class Convert
|
||||
{
|
||||
byte[][]? _aacsDecryptedCpsUnitKeys;
|
||||
ErrorNumber ConvertOptical(IOpticalMediaImage inputOptical, IWritableOpticalImage outputOptical, bool useLong)
|
||||
{
|
||||
if(!outputOptical.SetTracks(inputOptical.Tracks))
|
||||
@@ -150,8 +150,39 @@ public partial class Convert
|
||||
if(_aborted) return ErrorNumber.NoError;
|
||||
InitProgress?.Invoke();
|
||||
InitProgress2?.Invoke();
|
||||
byte[] generatedTitleKeys = null;
|
||||
var currentTrack = 0;
|
||||
var currentTrack = 0;
|
||||
|
||||
_aacsDecryptedCpsUnitKeys = null;
|
||||
|
||||
if(_decrypt)
|
||||
{
|
||||
if(IsHdDvdAacsMedia(inputOptical.Info.MediaType))
|
||||
{
|
||||
StoppingErrorMessage?.Invoke(Aaru.Localization.Core.Aacs_hddvd_not_supported);
|
||||
|
||||
return ErrorNumber.UnsupportedMedia;
|
||||
}
|
||||
|
||||
if(IsBluRayAacsMedia(inputOptical.Info.MediaType))
|
||||
{
|
||||
_aacsDecryptedCpsUnitKeys = AacsKeyResolver.TryGetDecryptedCpsUnitKeys(inputOptical,
|
||||
_plugins,
|
||||
Encoding.UTF8,
|
||||
out string aacsErr);
|
||||
|
||||
if(_aacsDecryptedCpsUnitKeys == null)
|
||||
{
|
||||
StoppingErrorMessage?.Invoke(aacsErr ?? Aaru.Localization.Core.Aacs_missing_unit_keys);
|
||||
|
||||
return ErrorNumber.NoData;
|
||||
}
|
||||
|
||||
return ConvertAacsBdOpticalSectors(inputOptical, outputOptical);
|
||||
}
|
||||
|
||||
if(CssDvdSectorDecrypt.IsDvdMedia(inputOptical.Info.MediaType))
|
||||
return ConvertCssDvdOpticalSectors(inputOptical, outputOptical, useLong);
|
||||
}
|
||||
|
||||
foreach(Track track in inputOptical.Tracks)
|
||||
{
|
||||
@@ -264,17 +295,6 @@ public partial class Convert
|
||||
out sector,
|
||||
out sectorStatusArray);
|
||||
|
||||
// TODO: Move to generic place when anything but CSS DVDs can be decrypted
|
||||
if(IsDvdMedia(inputOptical.Info.MediaType) && _decrypt)
|
||||
{
|
||||
DecryptDvdSector(ref sector,
|
||||
inputOptical,
|
||||
doneSectors + track.StartSector,
|
||||
sectorsToDo,
|
||||
_plugins,
|
||||
ref generatedTitleKeys);
|
||||
}
|
||||
|
||||
if(errno == ErrorNumber.NoError)
|
||||
{
|
||||
result = sectorsToDo == 1
|
||||
@@ -625,97 +645,34 @@ public partial class Convert
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decrypts DVD sectors using CSS (Content Scramble System) decryption
|
||||
/// Retrieves decryption keys from sector tags or generates them from ISO9660 filesystem
|
||||
/// Only MPEG packets within sectors can be encrypted
|
||||
/// Checks if media type is a Blu-ray.
|
||||
/// Includes media that may be encrypted with AACS (as well as some that cannot,
|
||||
/// in case of misidentifications (e.g. BDR)).
|
||||
/// </summary>
|
||||
void DecryptDvdSector(ref byte[] sector, IOpticalMediaImage inputOptical, ulong sectorAddress, uint sectorsToDo,
|
||||
PluginRegister plugins, ref byte[] generatedTitleKeys)
|
||||
{
|
||||
if(_aborted) return;
|
||||
|
||||
// Only sectors which are MPEG packets can be encrypted.
|
||||
if(!Mpeg.ContainsMpegPackets(sector, sectorsToDo)) return;
|
||||
|
||||
byte[] cmi, titleKey;
|
||||
|
||||
if(sectorsToDo == 1)
|
||||
{
|
||||
if(inputOptical.ReadSectorTag(sectorAddress, false, SectorTagType.DvdSectorCmi, out cmi) ==
|
||||
ErrorNumber.NoError &&
|
||||
inputOptical.ReadSectorTag(sectorAddress, false, SectorTagType.DvdTitleKeyDecrypted, out titleKey) ==
|
||||
ErrorNumber.NoError)
|
||||
sector = CSS.DecryptSector(sector, titleKey, cmi);
|
||||
else
|
||||
{
|
||||
if(generatedTitleKeys == null) GenerateDvdTitleKeys(inputOptical, plugins, ref generatedTitleKeys);
|
||||
|
||||
if(generatedTitleKeys != null)
|
||||
{
|
||||
sector = CSS.DecryptSector(sector,
|
||||
generatedTitleKeys.Skip((int)(5 * sectorAddress)).Take(5).ToArray(),
|
||||
null);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(inputOptical.ReadSectorsTag(sectorAddress, false, sectorsToDo, SectorTagType.DvdSectorCmi, out cmi) ==
|
||||
ErrorNumber.NoError &&
|
||||
inputOptical.ReadSectorsTag(sectorAddress,
|
||||
false,
|
||||
sectorsToDo,
|
||||
SectorTagType.DvdTitleKeyDecrypted,
|
||||
out titleKey) ==
|
||||
ErrorNumber.NoError)
|
||||
sector = CSS.DecryptSector(sector, titleKey, cmi, sectorsToDo);
|
||||
else
|
||||
{
|
||||
if(generatedTitleKeys == null) GenerateDvdTitleKeys(inputOptical, plugins, ref generatedTitleKeys);
|
||||
|
||||
if(generatedTitleKeys != null)
|
||||
{
|
||||
sector = CSS.DecryptSector(sector,
|
||||
generatedTitleKeys.Skip((int)(5 * sectorAddress))
|
||||
.Take((int)(5 * sectorsToDo))
|
||||
.ToArray(),
|
||||
null,
|
||||
sectorsToDo);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/// <param name="mediaType">Media type to check.</param>
|
||||
/// <returns>True if media type is a Blu-ray, false otherwise.</returns>
|
||||
static bool IsBluRayAacsMedia(MediaType mediaType) =>
|
||||
mediaType is MediaType.BDROM
|
||||
or MediaType.BDR
|
||||
or MediaType.BDRE
|
||||
or MediaType.BDRXL
|
||||
or MediaType.BDREXL
|
||||
or MediaType.UHDBD;
|
||||
|
||||
/// <summary>
|
||||
/// Generates DVD CSS title keys from ISO9660 filesystem
|
||||
/// Used when explicit title keys are not available in sector tags
|
||||
/// Searches for ISO9660 partitions to derive decryption keys
|
||||
/// Checks if media type is any variant of HD DVD (ROM, RAM, R, RW, RDL, PR, PRDL).
|
||||
/// Includes media that may be encrypted with AACS (as well as some that cannot,
|
||||
/// in case of misidentifications (e.g. HDDVDR)).
|
||||
/// </summary>
|
||||
void GenerateDvdTitleKeys(IOpticalMediaImage inputOptical, PluginRegister plugins, ref byte[] generatedTitleKeys)
|
||||
{
|
||||
if(_aborted) return;
|
||||
|
||||
List<Partition> partitions = Partitions.GetAll(inputOptical);
|
||||
|
||||
partitions = partitions.FindAll(p =>
|
||||
{
|
||||
Filesystems.Identify(inputOptical, out List<string> idPlugins, p);
|
||||
|
||||
return idPlugins.Contains("iso9660 filesystem");
|
||||
});
|
||||
|
||||
if(!plugins.ReadOnlyFilesystems.TryGetValue("iso9660 filesystem", out IReadOnlyFilesystem rofs)) return;
|
||||
|
||||
AaruLogging.Debug(MODULE_NAME, UI.Generating_decryption_keys);
|
||||
|
||||
generatedTitleKeys = CSS.GenerateTitleKeys(inputOptical, partitions, inputOptical.Info.Sectors, rofs);
|
||||
}
|
||||
|
||||
bool IsDvdMedia(MediaType mediaType) =>
|
||||
|
||||
// Checks if media type is any variant of DVD (ROM, R, RDL, PR, PRDL)
|
||||
// Consolidates media type checking logic used throughout conversion process
|
||||
mediaType is MediaType.DVDROM or MediaType.DVDR or MediaType.DVDRDL or MediaType.DVDPR or MediaType.DVDPRDL;
|
||||
/// <param name="mediaType">Media type to check.</param>
|
||||
/// <returns>True if media type is any variant of HD DVD, false otherwise.</returns>
|
||||
static bool IsHdDvdAacsMedia(MediaType mediaType) =>
|
||||
mediaType is MediaType.HDDVDROM
|
||||
or MediaType.HDDVDRAM
|
||||
or MediaType.HDDVDR
|
||||
or MediaType.HDDVDRW
|
||||
or MediaType.HDDVDRDL
|
||||
or MediaType.HDDVDRWDL;
|
||||
|
||||
private bool IsCompactDiscMedia(MediaType mediaType) =>
|
||||
|
||||
|
||||
@@ -624,13 +624,26 @@ public class CSS
|
||||
/// <param name="keyData">The encryption keys.</param>
|
||||
/// <param name="blocks">Number of sectors in <c>sectorData</c>.</param>
|
||||
/// <param name="blockSize">Size of one sector.</param>
|
||||
/// <param name="blockAppliedCssDecrypt">
|
||||
/// If non-null and <c>Length >= blocks</c>, each element is set to <c>true</c> when CSS unscrambling was
|
||||
/// applied to that logical block, and <c>false</c> when the sector was left unchanged (including the global
|
||||
/// "no encryption" early return and per-block <see cref="IsEncrypted"/> false).
|
||||
/// </param>
|
||||
/// <returns>The decrypted sector.</returns>
|
||||
public static byte[] DecryptSector(byte[] sectorData, byte[] keyData, byte[]? cmiData, uint blocks = 1,
|
||||
uint blockSize = 2048)
|
||||
uint blockSize = 2048, bool[]? blockAppliedCssDecrypt = null)
|
||||
{
|
||||
// None of the sectors are encrypted
|
||||
if(cmiData != null && cmiData.All(static cmi => (cmi & 0x80) >> 7 == 0) || keyData.All(static k => k == 0))
|
||||
if((cmiData != null && cmiData.All(static cmi => (cmi & 0x80) >> 7 == 0)) || keyData.All(static k => k == 0))
|
||||
{
|
||||
if(blockAppliedCssDecrypt != null)
|
||||
{
|
||||
for(uint i = 0; i < blocks && i < (uint)blockAppliedCssDecrypt.Length; i++)
|
||||
blockAppliedCssDecrypt[i] = false;
|
||||
}
|
||||
|
||||
return sectorData;
|
||||
}
|
||||
|
||||
var decryptedBuffer = new byte[sectorData.Length];
|
||||
|
||||
@@ -641,11 +654,17 @@ public class CSS
|
||||
|
||||
if(!IsEncrypted(cmiData?[i], currentKey, currentSector))
|
||||
{
|
||||
if(blockAppliedCssDecrypt != null && i < (uint)blockAppliedCssDecrypt.Length)
|
||||
blockAppliedCssDecrypt[i] = false;
|
||||
|
||||
Array.Copy(currentSector, 0, decryptedBuffer, (int)(i * blockSize), blockSize);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if(blockAppliedCssDecrypt != null && i < (uint)blockAppliedCssDecrypt.Length)
|
||||
blockAppliedCssDecrypt[i] = true;
|
||||
|
||||
Array.Copy(UnscrambleSector(currentKey, currentSector),
|
||||
0,
|
||||
decryptedBuffer,
|
||||
@@ -662,13 +681,26 @@ public class CSS
|
||||
/// <param name="keyData">The encryption keys.</param>
|
||||
/// <param name="blocks">Number of sectors in <c>sectorData</c>.</param>
|
||||
/// <param name="blockSize">Size of one sector.</param>
|
||||
/// <param name="blockAppliedCssDecrypt">
|
||||
/// If non-null and <c>Length >= blocks</c>, each element is set to <c>true</c> when CSS unscrambling was
|
||||
/// applied to that logical block, and <c>false</c> when the sector was left unchanged (including the global
|
||||
/// "no encryption" early return and per-block <see cref="IsEncrypted"/> false).
|
||||
/// </param>
|
||||
/// <returns>The decrypted sector.</returns>
|
||||
public static byte[] DecryptSectorLong(byte[] sectorData, byte[] keyData, byte[]? cmiData, uint blocks = 1,
|
||||
uint blockSize = 2048)
|
||||
uint blockSize = 2048, bool[]? blockAppliedCssDecrypt = null)
|
||||
{
|
||||
// None of the sectors are encrypted
|
||||
if(cmiData != null && cmiData.All(static cmi => (cmi & 0x80) >> 7 == 0) || keyData.All(static k => k == 0))
|
||||
if((cmiData != null && cmiData.All(static cmi => (cmi & 0x80) >> 7 == 0)) || keyData.All(static k => k == 0))
|
||||
{
|
||||
if(blockAppliedCssDecrypt != null)
|
||||
{
|
||||
for(uint i = 0; i < blocks && i < (uint)blockAppliedCssDecrypt.Length; i++)
|
||||
blockAppliedCssDecrypt[i] = false;
|
||||
}
|
||||
|
||||
return sectorData;
|
||||
}
|
||||
|
||||
var decryptedBuffer = new byte[sectorData.Length];
|
||||
|
||||
@@ -684,11 +716,17 @@ public class CSS
|
||||
|
||||
if(!IsEncrypted(cmiData?[i], currentKey, currentSector))
|
||||
{
|
||||
if(blockAppliedCssDecrypt != null && i < (uint)blockAppliedCssDecrypt.Length)
|
||||
blockAppliedCssDecrypt[i] = false;
|
||||
|
||||
Array.Copy(currentSector, 0, decryptedBuffer, (int)(12 + i * blockSize + 16 * i), blockSize);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if(blockAppliedCssDecrypt != null && i < (uint)blockAppliedCssDecrypt.Length)
|
||||
blockAppliedCssDecrypt[i] = true;
|
||||
|
||||
Array.Copy(UnscrambleSector(currentKey, currentSector),
|
||||
0,
|
||||
decryptedBuffer,
|
||||
@@ -1024,7 +1062,7 @@ public class CSS
|
||||
/// <param name="fs"><c>IReadOnlyFilesystem</c> to check in.</param>
|
||||
/// <param name="partition"><c>Partition</c> to check in.</param>
|
||||
/// <returns><c>true</c> if <c>VIDEO_TS</c> folder was found.</returns>
|
||||
static bool HasVideoTsFolder(IOpticalMediaImage input, IReadOnlyFilesystem fs, Partition partition)
|
||||
public static bool HasVideoTsFolder(IOpticalMediaImage input, IReadOnlyFilesystem fs, Partition partition)
|
||||
{
|
||||
ErrorNumber error = fs.Mount(input, partition, null, null, null);
|
||||
|
||||
|
||||
@@ -143,6 +143,27 @@ public class Mpeg
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>DVD raw long sector size (sync/header + 2048 user + 4 suffix) per CSS long decrypt.</summary>
|
||||
public const uint DvdLongSectorStride = 2064;
|
||||
|
||||
/// <summary>User data starts at this offset inside each <see cref="DvdLongSectorStride"/>-byte block.</summary>
|
||||
public const uint DvdLongSectorUserOffset = 12;
|
||||
|
||||
/// <summary>True if any logical DVD long sector in the buffer contains an MPEG pack header at the user payload offset.</summary>
|
||||
public static bool ContainsMpegPacketsLong(byte[] sectorData, uint blocks)
|
||||
{
|
||||
if(sectorData.Length < (long)blocks * (long)DvdLongSectorStride) return false;
|
||||
|
||||
for(uint i = 0; i < blocks; i++)
|
||||
{
|
||||
int off = (int)(DvdLongSectorUserOffset + i * DvdLongSectorStride);
|
||||
|
||||
if(IsMpegPacket(sectorData.Skip(off))) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool IsMpegPacket(IEnumerable<byte> sector) =>
|
||||
sector.Take(3).ToArray().SequenceEqual(_mpeg2PackHeaderStartCode);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user