From 7f3502e80f1cde4efb03b523a0c3fd0067c397bd Mon Sep 17 00:00:00 2001 From: Rebecca Wallander Date: Sat, 11 Apr 2026 16:09:16 +0200 Subject: [PATCH] [merge] Move decryption to own pipelines --- Aaru.Core/Image/Merge/MergeAacsBd.cs | 57 +++++ Aaru.Core/Image/Merge/MergeCssDvd.cs | 313 +++++++++++++++++++++++++++ Aaru.Core/Image/Merge/Merger.cs | 1 + Aaru.Core/Image/Merge/Optical.cs | 164 +++++--------- 4 files changed, 423 insertions(+), 112 deletions(-) create mode 100644 Aaru.Core/Image/Merge/MergeAacsBd.cs create mode 100644 Aaru.Core/Image/Merge/MergeCssDvd.cs diff --git a/Aaru.Core/Image/Merge/MergeAacsBd.cs b/Aaru.Core/Image/Merge/MergeAacsBd.cs new file mode 100644 index 000000000..9c6036bce --- /dev/null +++ b/Aaru.Core/Image/Merge/MergeAacsBd.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using Aaru.CommonTypes; +using Aaru.CommonTypes.Enums; +using Aaru.CommonTypes.Interfaces; +using Aaru.Localization; + +namespace Aaru.Core.Image; + +public sealed partial class Merger +{ + /// Merges Blu-ray AACS optical sectors. + /// Input optical media image. + /// Output optical media image. + /// Error number. + ErrorNumber CopyAacsBdOpticalSectorsPrimary(IOpticalMediaImage inputOptical, IWritableOpticalImage outputOptical) + { + ErrorNumber errno = AacsBdExtentResolver.ResolveStreamFileExtents(inputOptical, + _plugins, + out List ranges, + out string err); + + if(errno != ErrorNumber.NoError) + { + StoppingErrorMessage?.Invoke(err); + + return errno; + } + + Func allowDecryptLba = lba => AacsBdExtentResolver.IsLbaAllowed(ranges, lba); + + return AacsBdOpticalPipeline.Run(inputOptical, + outputOptical, + (uint)count, + false, + ref _aborted, + _aacsDecryptedCpsUnitKeys, + allowDecryptLba, + new AacsBdOpticalPipeline.Ui + { + OnInitProgress = () => InitProgress?.Invoke(), + OnInitProgress2 = () => InitProgress2?.Invoke(), + OnEndProgress = () => EndProgress?.Invoke(), + OnEndProgress2 = () => EndProgress2?.Invoke(), + OnTrackProgress = (i, n) => UpdateProgress?.Invoke(string.Format(UI.Copying_sectors_in_track_0_of_1, i + 1, n), i, n), + OnSectorRangeProgress = (start, end, seq, done, tot) => + UpdateProgress2?.Invoke(string.Format(UI.Copying_sectors_0_to_1_in_track_2, + start, + end, + seq), + done, + tot), + ErrorMessage = s => ErrorMessage?.Invoke(s), + StoppingErrorMessage = s => StoppingErrorMessage?.Invoke(s) + }); + } +} diff --git a/Aaru.Core/Image/Merge/MergeCssDvd.cs b/Aaru.Core/Image/Merge/MergeCssDvd.cs new file mode 100644 index 000000000..09fd8860f --- /dev/null +++ b/Aaru.Core/Image/Merge/MergeCssDvd.cs @@ -0,0 +1,313 @@ +// /*************************************************************************** +// Aaru Data Preservation Suite +// ---------------------------------------------------------------------------- +// +// --[ Description ] ---------------------------------------------------------- +// +// DVD CSS optical sector merge pipeline. +// +// ---------------------------------------------------------------------------- + +using System.Collections.Generic; +using System.Linq; +using Aaru.CommonTypes; +using Aaru.CommonTypes.Enums; +using Aaru.CommonTypes.Interfaces; +using Aaru.CommonTypes.Structs; +using Aaru.Localization; + +namespace Aaru.Core.Image; + +public sealed partial class Merger +{ + /// Merges CSS-encrypted DVD optical sectors. + /// Input optical media image. + /// Output optical media image. + /// True to use long sector reads. + /// Error number. + ErrorNumber CopyCssDvdOpticalSectorsPrimary(IOpticalMediaImage inputOptical, IWritableOpticalImage outputOptical, + bool useLong) + { + if(_aborted) return ErrorNumber.NoError; + + InitProgress?.Invoke(); + InitProgress2?.Invoke(); + byte[] generatedTitleKeys = null; + int currentTrack = 0; + + foreach(Track track in inputOptical.Tracks) + { + if(_aborted) break; + + UpdateProgress?.Invoke(string.Format(UI.Copying_sectors_in_track_0_of_1, + currentTrack + 1, + inputOptical.Tracks.Count), + currentTrack, + inputOptical.Tracks.Count); + + ulong doneSectors = 0; + ulong trackSectors = track.EndSector - track.StartSector + 1; + + while(doneSectors < trackSectors) + { + if(_aborted) break; + + byte[] sector; + + uint sectorsToDo; + + if(trackSectors - doneSectors >= (ulong)count) + sectorsToDo = (uint)count; + else + sectorsToDo = (uint)(trackSectors - doneSectors); + + UpdateProgress2?.Invoke(string.Format(UI.Copying_sectors_0_to_1_in_track_2, + doneSectors + track.StartSector, + doneSectors + sectorsToDo + track.StartSector, + track.Sequence), + (long)doneSectors, + (long)trackSectors); + + bool result; + SectorStatus sectorStatus = SectorStatus.NotDumped; + SectorStatus[] sectorStatusArray = new SectorStatus[1]; + ErrorNumber errno; + + if(useLong) + { + errno = sectorsToDo == 1 + ? inputOptical.ReadSectorLong(doneSectors + track.StartSector, + false, + out sector, + out sectorStatus) + : inputOptical.ReadSectorsLong(doneSectors + track.StartSector, + false, + sectorsToDo, + out sector, + out sectorStatusArray); + + if(errno == ErrorNumber.NoError) + { + CssDvdSectorDecrypt.ApplyCssAfterReadLong(ref sector, + ref sectorStatus, + sectorStatusArray, + sectorsToDo, + sectorsToDo == 1, + inputOptical, + doneSectors + track.StartSector, + _plugins, + ref generatedTitleKeys, + () => _aborted, + MODULE_NAME); + + result = sectorsToDo == 1 + ? outputOptical.WriteSectorLong(sector, + doneSectors + track.StartSector, + false, + sectorStatus) + : outputOptical.WriteSectorsLong(sector, + doneSectors + track.StartSector, + false, + sectorsToDo, + sectorStatusArray); + } + else + { + StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_reading_sector_1_not_continuing, + errno, + doneSectors + track.StartSector)); + + return ErrorNumber.WriteError; + } + + if(!result && sector.Length % 2352 != 0) + { + StoppingErrorMessage?.Invoke(UI.Input_image_is_not_returning_long_sectors_not_continuing); + + return ErrorNumber.InOutError; + } + } + else + { + errno = sectorsToDo == 1 + ? inputOptical.ReadSector(doneSectors + track.StartSector, + false, + out sector, + out sectorStatus) + : inputOptical.ReadSectors(doneSectors + track.StartSector, + false, + sectorsToDo, + out sector, + out sectorStatusArray); + + if(errno == ErrorNumber.NoError) + { + CssDvdSectorDecrypt.ApplyCssAfterRead(ref sector, + ref sectorStatus, + sectorStatusArray, + sectorsToDo, + sectorsToDo == 1, + inputOptical, + doneSectors + track.StartSector, + _plugins, + ref generatedTitleKeys, + () => _aborted, + MODULE_NAME); + + result = sectorsToDo == 1 + ? outputOptical.WriteSector(sector, + doneSectors + track.StartSector, + false, + sectorStatus) + : outputOptical.WriteSectors(sector, + doneSectors + track.StartSector, + false, + sectorsToDo, + sectorStatusArray); + } + else + { + StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_reading_sector_1_not_continuing, + errno, + doneSectors + track.StartSector)); + + return ErrorNumber.WriteError; + } + } + + if(!result) + { + StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_writing_sector_1_not_continuing, + outputOptical.ErrorMessage, + doneSectors + track.StartSector)); + + return ErrorNumber.WriteError; + } + + doneSectors += sectorsToDo; + } + + currentTrack++; + } + + EndProgress2?.Invoke(); + EndProgress?.Invoke(); + + return ErrorNumber.NoError; + } + + /// Merges CSS-encrypted DVD optical sectors from a secondary image. + /// Input optical media image. + /// Output optical media image. + /// True to use long sector reads. + /// Sectors to copy. + /// Error number. + ErrorNumber CopyCssDvdOpticalSectorsSecondary(IOpticalMediaImage inputOptical, IWritableOpticalImage outputOptical, + bool useLong, List sectorsToCopy) + { + if(_aborted) return ErrorNumber.NoError; + + InitProgress?.Invoke(); + byte[] generatedTitleKeys = null; + int howManySectorsToCopy = sectorsToCopy.Count(t => t < inputOptical.Info.Sectors); + int howManySectorsCopied = 0; + + foreach(ulong sectorAddress in sectorsToCopy.Where(t => t < inputOptical.Info.Sectors) + .TakeWhile(_ => !_aborted)) + { + UpdateProgress?.Invoke(string.Format(UI.Copying_sector_0, sectorAddress), + howManySectorsCopied, + howManySectorsToCopy); + + if(_aborted) break; + + byte[] sector; + bool result; + SectorStatus sectorStatus; + ErrorNumber errno; + + if(useLong) + { + errno = inputOptical.ReadSectorLong(sectorAddress, false, out sector, out sectorStatus); + + if(errno == ErrorNumber.NoError) + { + SectorStatus[] sectorStatusArray = new SectorStatus[1]; + CssDvdSectorDecrypt.ApplyCssAfterReadLong(ref sector, + ref sectorStatus, + sectorStatusArray, + 1, + true, + inputOptical, + sectorAddress, + _plugins, + ref generatedTitleKeys, + () => _aborted, + MODULE_NAME); + + result = outputOptical.WriteSectorLong(sector, sectorAddress, false, sectorStatus); + } + else + { + StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_reading_sector_1_not_continuing, + errno, + sectorAddress)); + + return ErrorNumber.WriteError; + } + + if(!result && sector.Length % 2352 != 0) + { + StoppingErrorMessage?.Invoke(UI.Input_image_is_not_returning_long_sectors_not_continuing); + + return ErrorNumber.InOutError; + } + } + else + { + errno = inputOptical.ReadSector(sectorAddress, false, out sector, out sectorStatus); + + if(errno == ErrorNumber.NoError) + { + SectorStatus[] sectorStatusArray = new SectorStatus[1]; + CssDvdSectorDecrypt.ApplyCssAfterRead(ref sector, + ref sectorStatus, + sectorStatusArray, + 1, + true, + inputOptical, + sectorAddress, + _plugins, + ref generatedTitleKeys, + () => _aborted, + MODULE_NAME); + + result = outputOptical.WriteSector(sector, sectorAddress, false, sectorStatus); + } + else + { + StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_reading_sector_1_not_continuing, + errno, + sectorAddress)); + + return ErrorNumber.WriteError; + } + } + + if(!result) + { + StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_writing_sector_1_not_continuing, + outputOptical.ErrorMessage, + sectorAddress)); + + return ErrorNumber.WriteError; + } + + howManySectorsCopied++; + } + + EndProgress?.Invoke(); + + return ErrorNumber.NoError; + } +} diff --git a/Aaru.Core/Image/Merge/Merger.cs b/Aaru.Core/Image/Merge/Merger.cs index de7693a30..81c121564 100644 --- a/Aaru.Core/Image/Merge/Merger.cs +++ b/Aaru.Core/Image/Merge/Merger.cs @@ -55,6 +55,7 @@ public sealed partial class Merger const string MODULE_NAME = "Image merger"; bool _aborted; readonly PluginRegister _plugins = PluginRegister.Singleton; + byte[][]? _aacsDecryptedCpsUnitKeys; public ErrorNumber Start() { diff --git a/Aaru.Core/Image/Merge/Optical.cs b/Aaru.Core/Image/Merge/Optical.cs index 4895bcf9e..269ce4cf8 100644 --- a/Aaru.Core/Image/Merge/Optical.cs +++ b/Aaru.Core/Image/Merge/Optical.cs @@ -6,10 +6,9 @@ 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; @@ -173,8 +172,39 @@ public sealed partial class Merger 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 CopyAacsBdOpticalSectorsPrimary(inputOptical, outputOptical); + } + + if(CssDvdSectorDecrypt.IsDvdMedia(inputOptical.Info.MediaType)) + return CopyCssDvdOpticalSectorsPrimary(inputOptical, outputOptical, useLong); + } foreach(Track track in inputOptical.Tracks) { @@ -269,17 +299,6 @@ public sealed partial class Merger 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 @@ -329,9 +348,11 @@ public sealed partial class Merger { if(_aborted) return ErrorNumber.NoError; + if(decrypt && CssDvdSectorDecrypt.IsDvdMedia(inputOptical.Info.MediaType)) + return CopyCssDvdOpticalSectorsSecondary(inputOptical, outputOptical, useLong, sectorsToCopy); + InitProgress?.Invoke(); - byte[] generatedTitleKeys = null; - int howManySectorsToCopy = sectorsToCopy.Count(t => t < inputOptical.Info.Sectors); + int howManySectorsToCopy = sectorsToCopy.Count(t => t < inputOptical.Info.Sectors); var howManySectorsCopied = 0; foreach(ulong sectorAddress in sectorsToCopy.Where(t => t < inputOptical.Info.Sectors) @@ -375,10 +396,6 @@ public sealed partial class Merger { errno = inputOptical.ReadSector(sectorAddress, false, out sector, out sectorStatus); - // TODO: Move to generic place when anything but CSS DVDs can be decrypted - if(IsDvdMedia(inputOptical.Info.MediaType) && decrypt) - DecryptDvdSector(ref sector, inputOptical, sectorAddress, 1, _plugins, ref generatedTitleKeys); - if(errno == ErrorNumber.NoError) result = outputOptical.WriteSector(sector, sectorAddress, false, sectorStatus); else @@ -753,98 +770,21 @@ public sealed partial class Merger return ErrorNumber.NoError; } - /// - /// 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 - /// - void DecryptDvdSector(ref byte[] sector, IOpticalMediaImage inputOptical, ulong sectorAddress, uint sectorsToDo, - PluginRegister plugins, ref byte[] generatedTitleKeys) - { - if(_aborted) return; + static bool IsBluRayAacsMedia(MediaType mediaType) => + mediaType is MediaType.BDROM + or MediaType.BDR + or MediaType.BDRE + or MediaType.BDRXL + or MediaType.BDREXL + or MediaType.UHDBD; - // 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); - } - } - } - } - - /// - /// 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 - /// - void GenerateDvdTitleKeys(IOpticalMediaImage inputOptical, PluginRegister plugins, ref byte[] generatedTitleKeys) - { - if(_aborted) return; - - List partitions = Partitions.GetAll(inputOptical); - - partitions = partitions.FindAll(p => - { - Filesystems.Identify(inputOptical, out List 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); - } - - static 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; + 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 static bool IsCompactDiscMedia(MediaType mediaType) =>