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) =>