mirror of
https://github.com/aaru-dps/Aaru.git
synced 2026-07-08 18:16:24 +00:00
Merge pull request #928 from aaru-dps/fakeshemp/aacs-2
Add AACS decryption when converting BDs
This commit is contained in:
222
Aaru.Core/Image/Convert/AACS/AacsBdExtentResolver.cs
Normal file
222
Aaru.Core/Image/Convert/AACS/AacsBdExtentResolver.cs
Normal file
@@ -0,0 +1,222 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Aaru.CommonTypes;
|
||||
using Aaru.CommonTypes.Enums;
|
||||
using Aaru.CommonTypes.Interfaces;
|
||||
using Aaru.CommonTypes.Structs;
|
||||
using Aaru.Filesystems;
|
||||
|
||||
namespace Aaru.Core.Image;
|
||||
|
||||
internal static class AacsBdExtentResolver
|
||||
{
|
||||
internal readonly struct LbaRange
|
||||
{
|
||||
internal ulong Start { get; init; }
|
||||
internal ulong End { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolves the stream file extents for a Blu-ray optical media image.
|
||||
/// It searches for the stream file extents in the <c>/BDMV/STREAM</c> and
|
||||
/// <c>/BDMV/STREAM/SSIF</c> directories.
|
||||
/// </summary>
|
||||
/// <param name="inputOptical">Input optical media image.</param>
|
||||
/// <param name="plugins">Plugin register.</param>
|
||||
/// <param name="ranges">Stream file extents.</param>
|
||||
/// <param name="error">Error message.</param>
|
||||
/// <returns>Error number.</returns>
|
||||
internal static ErrorNumber ResolveStreamFileExtents(IOpticalMediaImage inputOptical,
|
||||
PluginRegister plugins,
|
||||
out List<LbaRange> ranges,
|
||||
out string error)
|
||||
{
|
||||
ranges = [];
|
||||
error = null;
|
||||
|
||||
IReadOnlyFilesystem udfRo = null;
|
||||
UDF udf = null;
|
||||
|
||||
foreach(IFilesystem fs in plugins.Filesystems.Values)
|
||||
{
|
||||
if(fs is not UDF udfPlugin) continue;
|
||||
|
||||
Partition wholeImage = new()
|
||||
{
|
||||
Start = 0,
|
||||
Length = inputOptical.Info.Sectors,
|
||||
Size = inputOptical.Info.Sectors * inputOptical.Info.SectorSize,
|
||||
Sequence = 0,
|
||||
Type = "UDF"
|
||||
};
|
||||
|
||||
if(!udfPlugin.Identify(inputOptical, wholeImage)) continue;
|
||||
|
||||
ErrorNumber mountErr = udfPlugin.Mount(inputOptical, wholeImage, Encoding.UTF8, null, null);
|
||||
|
||||
if(mountErr != ErrorNumber.NoError)
|
||||
{
|
||||
error = "UDF mount failed while resolving Blu-ray stream extents.";
|
||||
|
||||
return mountErr;
|
||||
}
|
||||
|
||||
udfRo = udfPlugin;
|
||||
udf = udfPlugin;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if(udfRo is null || udf is null)
|
||||
{
|
||||
error = "Could not mount UDF filesystem for Blu-ray stream extent discovery.";
|
||||
|
||||
return ErrorNumber.NotSupported;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
ErrorNumber errno = AddDirectoryExtents(udfRo, udf, "/BDMV/STREAM", ".M2TS", ref ranges);
|
||||
|
||||
if(errno != ErrorNumber.NoError)
|
||||
{
|
||||
error = "Could not resolve extents for /BDMV/STREAM.";
|
||||
|
||||
return errno;
|
||||
}
|
||||
|
||||
errno = AddDirectoryExtents(udfRo, udf, "/BDMV/STREAM/SSIF", ".SSIF", ref ranges);
|
||||
|
||||
if(errno != ErrorNumber.NoError && errno != ErrorNumber.NoSuchFile)
|
||||
{
|
||||
error = "Could not resolve extents for /BDMV/STREAM/SSIF.";
|
||||
|
||||
return errno;
|
||||
}
|
||||
|
||||
if(ranges.Count == 0)
|
||||
{
|
||||
error = "No stream file extents found under /BDMV/STREAM.";
|
||||
|
||||
return ErrorNumber.NoData;
|
||||
}
|
||||
|
||||
ranges.Sort(static (a, b) => a.Start.CompareTo(b.Start));
|
||||
ranges = MergeRanges(ranges);
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
finally
|
||||
{
|
||||
udfRo.Unmount();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Checks if a given LBA is allowed for decryption.</summary>
|
||||
/// <param name="ranges">Stream file extents.</param>
|
||||
/// <param name="lba">LBA to check.</param>
|
||||
/// <returns>True if the LBA is allowed for decryption.</returns>
|
||||
internal static bool IsLbaAllowed(List<LbaRange> ranges, ulong lba)
|
||||
{
|
||||
int lo = 0;
|
||||
int hi = ranges.Count - 1;
|
||||
|
||||
while(lo <= hi)
|
||||
{
|
||||
int mid = lo + (hi - lo) / 2;
|
||||
LbaRange range = ranges[mid];
|
||||
|
||||
if(lba < range.Start)
|
||||
hi = mid - 1;
|
||||
else if(lba > range.End)
|
||||
lo = mid + 1;
|
||||
else
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>Adds the extents for a given directory to the list of stream file extents.</summary>
|
||||
/// <param name="roFs">Read-only filesystem.</param>
|
||||
/// <param name="udf">UDF filesystem.</param>
|
||||
/// <param name="dirPath">Directory path.</param>
|
||||
/// <param name="extension">Extension of the files to add.</param>
|
||||
/// <param name="ranges">List of stream file extents.</param>
|
||||
/// <returns>Error number.</returns>
|
||||
static ErrorNumber AddDirectoryExtents(IReadOnlyFilesystem roFs, UDF udf, string dirPath, string extension,
|
||||
ref List<LbaRange> ranges)
|
||||
{
|
||||
ErrorNumber errno = roFs.OpenDir(dirPath, out IDirNode dirNode);
|
||||
|
||||
if(errno != ErrorNumber.NoError) return errno;
|
||||
|
||||
try
|
||||
{
|
||||
while(true)
|
||||
{
|
||||
errno = roFs.ReadDir(dirNode, out string filename);
|
||||
|
||||
if(errno != ErrorNumber.NoError) return errno;
|
||||
|
||||
if(filename is null) break;
|
||||
|
||||
if(!filename.EndsWith(extension, StringComparison.OrdinalIgnoreCase)) continue;
|
||||
|
||||
string fullPath = dirPath + "/" + filename;
|
||||
|
||||
errno = udf.GetFilePhysicalSectorExtents(fullPath, out List<(ulong startSector, uint sectorCount)> extents);
|
||||
|
||||
if(errno != ErrorNumber.NoError) return errno;
|
||||
|
||||
foreach((ulong startSector, uint sectorCount) extent in extents)
|
||||
{
|
||||
if(extent.sectorCount == 0) continue;
|
||||
|
||||
ranges.Add(new LbaRange
|
||||
{
|
||||
Start = extent.startSector,
|
||||
End = extent.startSector + extent.sectorCount - 1
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
roFs.CloseDir(dirNode);
|
||||
}
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
|
||||
/// <summary>Merges the extents for a given directory to the list of stream file extents.</summary>
|
||||
/// <param name="ranges">List of stream file extents.</param>
|
||||
/// <returns>Merged list of stream file extents.</returns>
|
||||
static List<LbaRange> MergeRanges(List<LbaRange> ranges)
|
||||
{
|
||||
if(ranges.Count <= 1) return ranges;
|
||||
|
||||
List<LbaRange> merged = [];
|
||||
LbaRange current = ranges[0];
|
||||
|
||||
for(int i = 1; i < ranges.Count; i++)
|
||||
{
|
||||
LbaRange next = ranges[i];
|
||||
|
||||
if(next.Start <= current.End + 1)
|
||||
{
|
||||
current = new LbaRange { Start = current.Start, End = Math.Max(current.End, next.End) };
|
||||
continue;
|
||||
}
|
||||
|
||||
merged.Add(current);
|
||||
current = next;
|
||||
}
|
||||
|
||||
merged.Add(current);
|
||||
|
||||
return merged;
|
||||
}
|
||||
}
|
||||
472
Aaru.Core/Image/Convert/AACS/AacsBdOpticalPipeline.cs
Normal file
472
Aaru.Core/Image/Convert/AACS/AacsBdOpticalPipeline.cs
Normal file
@@ -0,0 +1,472 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Aaru.CommonTypes.Enums;
|
||||
using Aaru.CommonTypes.Interfaces;
|
||||
using Aaru.CommonTypes.Structs;
|
||||
using Aaru.Decryption.Aacs;
|
||||
using Aaru.Localization;
|
||||
|
||||
namespace Aaru.Core.Image;
|
||||
|
||||
/// <summary>Blu-ray AACS sector pipeline for convert/merge: LBA-aligned 6144-byte CPS units with output staging.</summary>
|
||||
internal static class AacsBdOpticalPipeline
|
||||
{
|
||||
internal readonly struct Ui
|
||||
{
|
||||
public Action OnInitProgress { get; init; }
|
||||
public Action OnInitProgress2 { get; init; }
|
||||
public Action OnEndProgress { get; init; }
|
||||
public Action OnEndProgress2 { get; init; }
|
||||
public Action<int, int> OnTrackProgress { get; init; }
|
||||
public Action<ulong, ulong, uint, long, long> OnSectorRangeProgress { get; init; }
|
||||
public Action<string> ErrorMessage { get; init; }
|
||||
public Action<string> StoppingErrorMessage { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>Runs the Blu-ray AACS sector pipeline.</summary>
|
||||
/// <param name="inputOptical">Input optical media image.</param>
|
||||
/// <param name="outputOptical">Output optical media image.</param>
|
||||
/// <param name="batchCount">Batch count.</param>
|
||||
/// <param name="force">Force flag.</param>
|
||||
/// <param name="aborted">Aborted flag.</param>
|
||||
/// <param name="decryptedCpsUnitKeys">Decrypted CPS unit keys.</param>
|
||||
/// <param name="allowDecryptLba">Allow decrypt LBA function.</param>
|
||||
/// <param name="ui">UI.</param>
|
||||
/// <returns>Error number.</returns>
|
||||
internal static ErrorNumber Run(IOpticalMediaImage inputOptical, IWritableOpticalImage outputOptical, uint batchCount,
|
||||
bool force, ref bool aborted, byte[][] decryptedCpsUnitKeys,
|
||||
Func<ulong, bool> allowDecryptLba, Ui ui)
|
||||
{
|
||||
ui.OnInitProgress?.Invoke();
|
||||
ui.OnInitProgress2?.Invoke();
|
||||
|
||||
int currentTrack = 0;
|
||||
|
||||
foreach(Track track in inputOptical.Tracks)
|
||||
{
|
||||
if(aborted) break;
|
||||
|
||||
ui.OnTrackProgress?.Invoke(currentTrack, inputOptical.Tracks.Count);
|
||||
|
||||
ulong doneSectors = 0;
|
||||
ulong trackSectors = track.EndSector - track.StartSector + 1;
|
||||
|
||||
List<byte[]> pending = [];
|
||||
List<ulong> pendingLba = [];
|
||||
List<SectorStatus> pendingSt = [];
|
||||
|
||||
while(doneSectors < trackSectors)
|
||||
{
|
||||
if(aborted) break;
|
||||
|
||||
uint sectorsToDo = trackSectors - doneSectors >= batchCount
|
||||
? batchCount
|
||||
: (uint)(trackSectors - doneSectors);
|
||||
|
||||
ui.OnSectorRangeProgress?.Invoke(doneSectors + track.StartSector,
|
||||
doneSectors + sectorsToDo + track.StartSector,
|
||||
track.Sequence,
|
||||
(long)doneSectors,
|
||||
(long)trackSectors);
|
||||
|
||||
ErrorNumber errno = inputOptical.ReadSectors(doneSectors + track.StartSector,
|
||||
false,
|
||||
sectorsToDo,
|
||||
out byte[] sectorData,
|
||||
out SectorStatus[] sectorStatusArray);
|
||||
|
||||
if(errno != ErrorNumber.NoError)
|
||||
{
|
||||
if(force)
|
||||
{
|
||||
ErrorNumber pend = FlushPendingOnReadSkip(outputOptical, ref pending, ref pendingLba,
|
||||
ref pendingSt, force, ui);
|
||||
|
||||
if(pend != ErrorNumber.NoError) return pend;
|
||||
|
||||
ui.ErrorMessage?.Invoke(string.Format(UI.Error_0_reading_sector_1_continuing,
|
||||
errno,
|
||||
doneSectors + track.StartSector));
|
||||
}
|
||||
else
|
||||
{
|
||||
if(pending.Count > 0)
|
||||
{
|
||||
ui.StoppingErrorMessage?.Invoke(string.Format(
|
||||
Aaru.Localization.Core.Aacs_incomplete_cps_unit_pending_before_read_error,
|
||||
pendingLba[0]));
|
||||
|
||||
return errno;
|
||||
}
|
||||
|
||||
ui.StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_reading_sector_1_not_continuing,
|
||||
errno,
|
||||
doneSectors + track.StartSector));
|
||||
|
||||
return errno;
|
||||
}
|
||||
|
||||
doneSectors += sectorsToDo;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
int bps = sectorData.Length / (int)sectorsToDo;
|
||||
|
||||
if(bps != AacsStreamDecrypt.SectorLen || sectorData.Length % sectorsToDo != 0)
|
||||
{
|
||||
ui.StoppingErrorMessage?.Invoke(string.Format(Aaru.Localization.Core.Aacs_bd_decrypt_requires_0_byte_sectors,
|
||||
bps));
|
||||
|
||||
return ErrorNumber.InOutError;
|
||||
}
|
||||
|
||||
for(uint i = 0; i < sectorsToDo; i++)
|
||||
{
|
||||
byte[] one = new byte[AacsStreamDecrypt.SectorLen];
|
||||
Buffer.BlockCopy(sectorData, (int)(i * AacsStreamDecrypt.SectorLen), one, 0, AacsStreamDecrypt.SectorLen);
|
||||
ulong lba = doneSectors + track.StartSector + i;
|
||||
SectorStatus st = sectorStatusArray[i];
|
||||
|
||||
bool allowDecrypt = allowDecryptLba is null || allowDecryptLba(lba);
|
||||
|
||||
ErrorNumber step = ProcessOneUserSector(outputOptical,
|
||||
decryptedCpsUnitKeys,
|
||||
one,
|
||||
lba,
|
||||
allowDecrypt,
|
||||
st,
|
||||
ref pending,
|
||||
ref pendingLba,
|
||||
ref pendingSt,
|
||||
force,
|
||||
ui);
|
||||
|
||||
if(step != ErrorNumber.NoError) return step;
|
||||
}
|
||||
|
||||
doneSectors += sectorsToDo;
|
||||
}
|
||||
|
||||
ErrorNumber flushErr = FlushPendingAtTrackEnd(outputOptical,
|
||||
ref pending,
|
||||
ref pendingLba,
|
||||
ref pendingSt,
|
||||
force,
|
||||
ui);
|
||||
|
||||
if(flushErr != ErrorNumber.NoError) return flushErr;
|
||||
|
||||
currentTrack++;
|
||||
}
|
||||
|
||||
ui.OnEndProgress2?.Invoke();
|
||||
ui.OnEndProgress?.Invoke();
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
|
||||
/// <summary>Flushes the pending sectors on read skip.</summary>
|
||||
/// <param name="outputOptical">Output optical media image.</param>
|
||||
/// <param name="pending">List of pending sectors.</param>
|
||||
/// <param name="pendingLba">List of pending LBA.</param>
|
||||
/// <param name="pendingSt">List of pending sector statuses.</param>
|
||||
/// <param name="force">Force flag.</param>
|
||||
/// <param name="ui">UI.</param>
|
||||
/// <returns>Error number.</returns>
|
||||
static ErrorNumber FlushPendingOnReadSkip(IWritableOpticalImage outputOptical, ref List<byte[]> pending,
|
||||
ref List<ulong> pendingLba, ref List<SectorStatus> pendingSt, bool force,
|
||||
Ui ui)
|
||||
{
|
||||
if(pending.Count == 0) return ErrorNumber.NoError;
|
||||
|
||||
ui.ErrorMessage?.Invoke(string.Format(Aaru.Localization.Core.Aacs_incomplete_cps_unit_after_read_error_continuing,
|
||||
pending.Count,
|
||||
pendingLba[0]));
|
||||
|
||||
for(int i = 0; i < pending.Count; i++)
|
||||
{
|
||||
ErrorNumber step = WriteOne(outputOptical, pendingLba[i], pending[i], SectorStatus.Dumped, force, ui);
|
||||
|
||||
if(step != ErrorNumber.NoError) return step;
|
||||
}
|
||||
|
||||
pending.Clear();
|
||||
pendingLba.Clear();
|
||||
pendingSt.Clear();
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
|
||||
/// <summary>Processes one user sector.</summary>
|
||||
/// <param name="outputOptical">Output optical media image.</param>
|
||||
/// <param name="decryptedCpsUnitKeys">Decrypted CPS unit keys.</param>
|
||||
/// <param name="sector">Sector data.</param>
|
||||
/// <param name="lba">LBA of the sector.</param>
|
||||
/// <param name="allowDecrypt">Allow decrypt flag.</param>
|
||||
/// <param name="readStatus">Read status of the sector.</param>
|
||||
/// <param name="pending">List of pending sectors.</param>
|
||||
/// <param name="pendingLba">List of pending LBA.</param>
|
||||
/// <param name="pendingSt">List of pending sector statuses.</param>
|
||||
/// <param name="force">Force flag.</param>
|
||||
/// <param name="ui">UI.</param>
|
||||
/// <returns>Error number.</returns>
|
||||
static ErrorNumber ProcessOneUserSector(IWritableOpticalImage outputOptical, byte[][] decryptedCpsUnitKeys,
|
||||
byte[] sector, ulong lba, bool allowDecrypt, SectorStatus readStatus,
|
||||
ref List<byte[]> pending,
|
||||
ref List<ulong> pendingLba, ref List<SectorStatus> pendingSt, bool force,
|
||||
Ui ui)
|
||||
{
|
||||
if(!allowDecrypt)
|
||||
{
|
||||
ErrorNumber flush = FlushPendingAsDumped(outputOptical, ref pending, ref pendingLba, ref pendingSt, force, ui);
|
||||
|
||||
if(flush != ErrorNumber.NoError) return flush;
|
||||
|
||||
return WriteOne(outputOptical, lba, sector, SectorStatus.Dumped, force, ui);
|
||||
}
|
||||
|
||||
if(pending.Count == 0)
|
||||
{
|
||||
if((sector[0] & 0xc0) == 0)
|
||||
return WriteOne(outputOptical, lba, sector, SectorStatus.Dumped, force, ui);
|
||||
|
||||
pending.Add(sector);
|
||||
pendingLba.Add(lba);
|
||||
pendingSt.Add(readStatus);
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
|
||||
pending.Add(sector);
|
||||
pendingLba.Add(lba);
|
||||
pendingSt.Add(readStatus);
|
||||
|
||||
if(pending.Count < 3) return ErrorNumber.NoError;
|
||||
|
||||
return FlushCompleteUnit(outputOptical,
|
||||
decryptedCpsUnitKeys,
|
||||
ref pending,
|
||||
ref pendingLba,
|
||||
ref pendingSt,
|
||||
force,
|
||||
ui);
|
||||
}
|
||||
|
||||
/// <summary>Flushes a complete unit.</summary>
|
||||
/// <param name="outputOptical">Output optical media image.</param>
|
||||
/// <param name="decryptedCpsUnitKeys">Decrypted CPS unit keys.</param>
|
||||
/// <param name="pending">List of pending sectors.</param>
|
||||
/// <param name="pendingLba">List of pending LBA.</param>
|
||||
/// <param name="pendingSt">List of pending sector statuses.</param>
|
||||
/// <param name="force">Force flag.</param>
|
||||
/// <param name="ui">UI.</param>
|
||||
/// <returns>Error number.</returns>
|
||||
static ErrorNumber FlushCompleteUnit(IWritableOpticalImage outputOptical, byte[][] decryptedCpsUnitKeys,
|
||||
ref List<byte[]> pending, ref List<ulong> pendingLba,
|
||||
ref List<SectorStatus> pendingSt, bool force, Ui ui)
|
||||
{
|
||||
byte[] unit = new byte[AacsStreamDecrypt.AlignedUnitLen];
|
||||
Buffer.BlockCopy(pending[0], 0, unit, 0, AacsStreamDecrypt.SectorLen);
|
||||
Buffer.BlockCopy(pending[1], 0, unit, AacsStreamDecrypt.SectorLen, AacsStreamDecrypt.SectorLen);
|
||||
Buffer.BlockCopy(pending[2], 0, unit, AacsStreamDecrypt.SectorLen * 2, AacsStreamDecrypt.SectorLen);
|
||||
|
||||
if(!AacsStreamDecrypt.TryDecryptAlignedUnit(unit, decryptedCpsUnitKeys))
|
||||
{
|
||||
if(!force)
|
||||
{
|
||||
ui.StoppingErrorMessage?.Invoke(string.Format(Aaru.Localization.Core.Aacs_could_not_decrypt_cps_unit_starting_at_0,
|
||||
pendingLba[0]));
|
||||
|
||||
return ErrorNumber.WriteError;
|
||||
}
|
||||
|
||||
ui.ErrorMessage?.Invoke(string.Format(Aaru.Localization.Core.Aacs_could_not_decrypt_cps_unit_starting_at_0_continuing,
|
||||
pendingLba[0]));
|
||||
|
||||
ErrorNumber w = WriteThree(outputOptical,
|
||||
pending[0],
|
||||
pending[1],
|
||||
pending[2],
|
||||
pendingLba[0],
|
||||
pendingSt[0],
|
||||
pendingSt[1],
|
||||
pendingSt[2],
|
||||
force,
|
||||
ui);
|
||||
|
||||
pending.Clear();
|
||||
pendingLba.Clear();
|
||||
pendingSt.Clear();
|
||||
|
||||
return w;
|
||||
}
|
||||
|
||||
SectorStatus[] ok = [SectorStatus.Unencrypted, SectorStatus.Unencrypted, SectorStatus.Unencrypted];
|
||||
ErrorNumber e = WriteSectorsFromBuffer(outputOptical, unit, pendingLba[0], ok, force, ui);
|
||||
|
||||
pending.Clear();
|
||||
pendingLba.Clear();
|
||||
pendingSt.Clear();
|
||||
|
||||
return e;
|
||||
}
|
||||
|
||||
/// <summary>Writes three sectors from a buffer.</summary>
|
||||
/// <param name="outputOptical">Output optical media image.</param>
|
||||
/// <param name="unit">Unit data.</param>
|
||||
/// <param name="firstLba">First LBA of the sectors.</param>
|
||||
/// <param name="threeStatuses">Statuses of the sectors.</param>
|
||||
/// <param name="force">Force flag.</param>
|
||||
/// <param name="ui">UI.</param>
|
||||
/// <returns>Error number.</returns>
|
||||
static ErrorNumber WriteSectorsFromBuffer(IWritableOpticalImage outputOptical, byte[] unit, ulong firstLba,
|
||||
SectorStatus[] threeStatuses, bool force, Ui ui)
|
||||
{
|
||||
bool result = outputOptical.WriteSectors(unit, firstLba, false, 3, threeStatuses);
|
||||
|
||||
if(!result)
|
||||
{
|
||||
if(force)
|
||||
{
|
||||
ui.ErrorMessage?.Invoke(string.Format(UI.Error_0_writing_sector_1_continuing,
|
||||
outputOptical.ErrorMessage,
|
||||
firstLba));
|
||||
}
|
||||
else
|
||||
{
|
||||
ui.StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_writing_sector_1_not_continuing,
|
||||
outputOptical.ErrorMessage,
|
||||
firstLba));
|
||||
|
||||
return ErrorNumber.WriteError;
|
||||
}
|
||||
}
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
|
||||
/// <summary>Flushes the pending sectors as dumped.</summary>
|
||||
/// <param name="outputOptical">Output optical media image.</param>
|
||||
/// <param name="pending">List of pending sectors.</param>
|
||||
/// <param name="pendingLba">List of pending LBA.</param>
|
||||
/// <param name="pendingSt">List of pending sector statuses.</param>
|
||||
/// <param name="force">Force flag.</param>
|
||||
/// <param name="ui">UI.</param>
|
||||
/// <returns>Error number.</returns>
|
||||
static ErrorNumber FlushPendingAsDumped(IWritableOpticalImage outputOptical, ref List<byte[]> pending,
|
||||
ref List<ulong> pendingLba, ref List<SectorStatus> pendingSt, bool force,
|
||||
Ui ui)
|
||||
{
|
||||
for(int i = 0; i < pending.Count; i++)
|
||||
{
|
||||
ErrorNumber step = WriteOne(outputOptical, pendingLba[i], pending[i], SectorStatus.Dumped, force, ui);
|
||||
|
||||
if(step != ErrorNumber.NoError) return step;
|
||||
}
|
||||
|
||||
pending.Clear();
|
||||
pendingLba.Clear();
|
||||
pendingSt.Clear();
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
|
||||
/// <summary>Writes three sectors from a buffer.</summary>
|
||||
/// <param name="outputOptical">Output optical media image.</param>
|
||||
/// <param name="a">First sector data.</param>
|
||||
/// <param name="b">Second sector data.</param>
|
||||
/// <param name="c">Third sector data.</param>
|
||||
/// <param name="firstLba">First LBA of the sectors.</param>
|
||||
/// <param name="threeStatuses">Statuses of the sectors.</param>
|
||||
/// <param name="force">Force flag.</param>
|
||||
/// <param name="ui">UI.</param>
|
||||
/// <returns>Error number.</returns>
|
||||
static ErrorNumber WriteThree(IWritableOpticalImage outputOptical, byte[] a, byte[] b, byte[] c, ulong firstLba,
|
||||
SectorStatus sa, SectorStatus sb, SectorStatus sc, bool force, Ui ui)
|
||||
{
|
||||
byte[] buf = new byte[AacsStreamDecrypt.AlignedUnitLen];
|
||||
Buffer.BlockCopy(a, 0, buf, 0, AacsStreamDecrypt.SectorLen);
|
||||
Buffer.BlockCopy(b, 0, buf, AacsStreamDecrypt.SectorLen, AacsStreamDecrypt.SectorLen);
|
||||
Buffer.BlockCopy(c, 0, buf, AacsStreamDecrypt.SectorLen * 2, AacsStreamDecrypt.SectorLen);
|
||||
|
||||
SectorStatus[] sts = [sa, sb, sc];
|
||||
|
||||
return WriteSectorsFromBuffer(outputOptical, buf, firstLba, sts, force, ui);
|
||||
}
|
||||
|
||||
/// <summary>Writes one sector.</summary>
|
||||
/// <param name="outputOptical">Output optical media image.</param>
|
||||
/// <param name="lba">LBA of the sector.</param>
|
||||
/// <param name="sector">Sector data.</param>
|
||||
/// <param name="status">Status of the sector.</param>
|
||||
/// <param name="force">Force flag.</param>
|
||||
/// <param name="ui">UI.</param>
|
||||
/// <returns>Error number.</returns>
|
||||
static ErrorNumber WriteOne(IWritableOpticalImage outputOptical, ulong lba, byte[] sector, SectorStatus status,
|
||||
bool force, Ui ui)
|
||||
{
|
||||
bool result = outputOptical.WriteSector(sector, lba, false, status);
|
||||
|
||||
if(!result)
|
||||
{
|
||||
if(force)
|
||||
{
|
||||
ui.ErrorMessage?.Invoke(string.Format(UI.Error_0_writing_sector_1_continuing,
|
||||
outputOptical.ErrorMessage,
|
||||
lba));
|
||||
}
|
||||
else
|
||||
{
|
||||
ui.StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_writing_sector_1_not_continuing,
|
||||
outputOptical.ErrorMessage,
|
||||
lba));
|
||||
|
||||
return ErrorNumber.WriteError;
|
||||
}
|
||||
}
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
|
||||
/// <summary>Flushes the pending sectors at track end.</summary>
|
||||
/// <param name="outputOptical">Output optical media image.</param>
|
||||
/// <param name="pending">List of pending sectors.</param>
|
||||
/// <param name="pendingLba">List of pending LBA.</param>
|
||||
/// <param name="pendingSt">List of pending sector statuses.</param>
|
||||
/// <param name="force">Force flag.</param>
|
||||
/// <param name="ui">UI.</param>
|
||||
/// <returns>Error number.</returns>
|
||||
static ErrorNumber FlushPendingAtTrackEnd(IWritableOpticalImage outputOptical, ref List<byte[]> pending,
|
||||
ref List<ulong> pendingLba, ref List<SectorStatus> pendingSt, bool force,
|
||||
Ui ui)
|
||||
{
|
||||
if(pending.Count == 0) return ErrorNumber.NoError;
|
||||
|
||||
if(!force)
|
||||
{
|
||||
ui.StoppingErrorMessage?.Invoke(string.Format(Aaru.Localization.Core.Aacs_incomplete_cps_unit_at_track_end,
|
||||
pending.Count,
|
||||
pendingLba[0]));
|
||||
|
||||
return ErrorNumber.WriteError;
|
||||
}
|
||||
|
||||
ui.ErrorMessage?.Invoke(string.Format(Aaru.Localization.Core.Aacs_incomplete_cps_unit_at_track_end_continuing,
|
||||
pending.Count,
|
||||
pendingLba[0]));
|
||||
|
||||
for(int i = 0; i < pending.Count; i++)
|
||||
{
|
||||
ErrorNumber step = WriteOne(outputOptical, pendingLba[i], pending[i], SectorStatus.Dumped, force, ui);
|
||||
|
||||
if(step != ErrorNumber.NoError) return step;
|
||||
}
|
||||
|
||||
pending.Clear();
|
||||
pendingLba.Clear();
|
||||
pendingSt.Clear();
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
}
|
||||
249
Aaru.Core/Image/Convert/AACS/AacsKeyResolver.cs
Normal file
249
Aaru.Core/Image/Convert/AACS/AacsKeyResolver.cs
Normal file
@@ -0,0 +1,249 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using Aaru.CommonTypes;
|
||||
using Aaru.CommonTypes.Enums;
|
||||
using Aaru.CommonTypes.Interfaces;
|
||||
using Aaru.CommonTypes.Structs;
|
||||
using Aaru.Decryption.Aacs;
|
||||
|
||||
namespace Aaru.Core.Image;
|
||||
|
||||
/// <summary>Loads VUK and decrypts CPS unit keys for Blu-ray AACS conversion.</summary>
|
||||
public static class AacsKeyResolver
|
||||
{
|
||||
static readonly string[] UnitKeyPaths =
|
||||
[
|
||||
"AACS/Unit_Key_RO.inf",
|
||||
"AACS\\Unit_Key_RO.inf"
|
||||
];
|
||||
|
||||
/// <summary>Checks if every byte in the buffer is zero.</summary>
|
||||
/// <param name="buffer">Buffer to check.</param>
|
||||
/// <returns>True if every byte in the buffer is zero.</returns>
|
||||
public static bool IsAllZero(ReadOnlySpan<byte> buffer)
|
||||
{
|
||||
for(int i = 0; i < buffer.Length; i++)
|
||||
{
|
||||
if(buffer[i] != 0)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Obtains decrypted 16-byte CPS unit keys in disc order, or <see langword="null"/> if keys cannot be loaded.
|
||||
/// </summary>
|
||||
/// <param name="image">Input optical media image.</param>
|
||||
/// <param name="plugins">Plugin register.</param>
|
||||
/// <param name="encoding">Encoding.</param>
|
||||
/// <param name="errorMessage">Error message.</param>
|
||||
/// <returns>Decrypted CPS unit keys.</returns>
|
||||
public static byte[][]? TryGetDecryptedCpsUnitKeys(IOpticalMediaImage image, PluginRegister plugins,
|
||||
Encoding? encoding, out string? errorMessage)
|
||||
{
|
||||
errorMessage = null;
|
||||
encoding ??= Encoding.UTF8;
|
||||
|
||||
if(!TryResolveVolumeUniqueKey(image, out byte[] vuk, out errorMessage))
|
||||
return null;
|
||||
|
||||
if(!TryLoadEncryptedCpsUnitKeys(image, plugins, encoding, out byte[][]? encKeys, out errorMessage) ||
|
||||
encKeys is null)
|
||||
return null;
|
||||
|
||||
byte[][] decrypted = new byte[encKeys.Length][];
|
||||
|
||||
for(int i = 0; i < encKeys.Length; i++)
|
||||
{
|
||||
if(encKeys[i].Length != 16)
|
||||
{
|
||||
errorMessage = Aaru.Localization.Core.Aacs_encrypted_unit_key_invalid_length;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
decrypted[i] = new byte[16];
|
||||
AacsCrypto.DecryptCpsUnitKey(vuk, encKeys[i], decrypted[i]);
|
||||
}
|
||||
|
||||
return decrypted;
|
||||
}
|
||||
|
||||
/// <summary>Tries to resolve the volume unique key from the media tags.</summary>
|
||||
/// <param name="image">Input optical media image.</param>
|
||||
/// <param name="vuk">Volume unique key.</param>
|
||||
/// <param name="errorMessage">Error message.</param>
|
||||
/// <returns>True if the volume unique key was resolved successfully.</returns>
|
||||
static bool TryResolveVolumeUniqueKey(IOpticalMediaImage image, out byte[] vuk, out string? errorMessage)
|
||||
{
|
||||
vuk = new byte[16];
|
||||
errorMessage = null;
|
||||
|
||||
if(image.ReadMediaTag(MediaTagType.AacsVolumeUniqueKey, out byte[]? tagVuk) == ErrorNumber.NoError &&
|
||||
tagVuk is { Length: 16 } &&
|
||||
!IsAllZero(tagVuk))
|
||||
{
|
||||
Buffer.BlockCopy(tagVuk, 0, vuk, 0, 16);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if(image.ReadMediaTag(MediaTagType.AacsMediaKey, out byte[]? mk) != ErrorNumber.NoError ||
|
||||
mk is not { Length: 16 } ||
|
||||
IsAllZero(mk))
|
||||
{
|
||||
errorMessage = Aaru.Localization.Core.Aacs_missing_volume_unique_key;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if(image.ReadMediaTag(MediaTagType.AACS_VolumeIdentifier, out byte[]? vid) != ErrorNumber.NoError ||
|
||||
vid is not { Length: 16 } ||
|
||||
IsAllZero(vid))
|
||||
{
|
||||
errorMessage = Aaru.Localization.Core.Aacs_missing_volume_unique_key;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
AacsCrypto.DeriveVolumeUniqueKey(mk, vid, vuk);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>Tries to load the encrypted CPS unit keys from the media tags or filesystem.</summary>
|
||||
/// <param name="image">Input optical media image.</param>
|
||||
/// <param name="plugins">Plugin register.</param>
|
||||
/// <param name="encoding">Encoding.</param>
|
||||
/// <param name="encKeys">Encrypted CPS unit keys.</param>
|
||||
/// <param name="errorMessage">Error message.</param>
|
||||
/// <returns>True if the encrypted CPS unit keys were loaded successfully.</returns>
|
||||
static bool TryLoadEncryptedCpsUnitKeys(IOpticalMediaImage image, PluginRegister plugins, Encoding encoding,
|
||||
out byte[][]? encKeys, out string? errorMessage)
|
||||
{
|
||||
encKeys = null;
|
||||
errorMessage = null;
|
||||
|
||||
if(image.ReadMediaTag(MediaTagType.AACS_DataKeys, out byte[]? dataKeys) == ErrorNumber.NoError &&
|
||||
dataKeys is { Length: > 0 })
|
||||
{
|
||||
UnitKeyRoParseResult? parsed = UnitKeyRoParseResult.TryParse(dataKeys);
|
||||
|
||||
if(parsed != null)
|
||||
{
|
||||
if(parsed.IsAacs2Layout)
|
||||
{
|
||||
errorMessage = Aaru.Localization.Core.Aacs2_unit_keys_not_supported;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
encKeys = parsed.EncryptedCpsUnitKeys;
|
||||
|
||||
if(encKeys.Length > 0)
|
||||
return true;
|
||||
}
|
||||
|
||||
UnitKeyRoParseResult? raw = UnitKeyRoParseResult.TryParseRawEncryptedKeys(dataKeys);
|
||||
|
||||
if(raw != null)
|
||||
{
|
||||
encKeys = raw.EncryptedCpsUnitKeys;
|
||||
|
||||
if(encKeys.Length > 0)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if(TryReadUnitKeyInfFromFilesystem(image, plugins, encoding, out byte[]? fileBytes) &&
|
||||
fileBytes is { Length: > 0 })
|
||||
{
|
||||
UnitKeyRoParseResult? parsed = UnitKeyRoParseResult.TryParse(fileBytes);
|
||||
|
||||
if(parsed == null)
|
||||
{
|
||||
errorMessage = Aaru.Localization.Core.Aacs_unit_key_inf_invalid;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if(parsed.IsAacs2Layout)
|
||||
{
|
||||
errorMessage = Aaru.Localization.Core.Aacs2_unit_keys_not_supported;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
encKeys = parsed.EncryptedCpsUnitKeys;
|
||||
|
||||
return encKeys.Length > 0;
|
||||
}
|
||||
|
||||
errorMessage = Aaru.Localization.Core.Aacs_missing_unit_keys;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>Tries to read the <c>Unit_Key_RO.inf</c> file from the filesystem.</summary>
|
||||
/// <param name="image">Input optical media image.</param>
|
||||
/// <param name="plugins">Plugin register.</param>
|
||||
/// <param name="encoding">Encoding.</param>
|
||||
/// <param name="fileBytes">File bytes.</param>
|
||||
/// <returns>True if the <c>Unit_Key_RO.inf</c> file was read successfully.</returns>
|
||||
static bool TryReadUnitKeyInfFromFilesystem(IOpticalMediaImage image, PluginRegister plugins, Encoding encoding,
|
||||
out byte[]? fileBytes)
|
||||
{
|
||||
fileBytes = null;
|
||||
|
||||
foreach(Partition partition in Partitions.GetAll(image))
|
||||
{
|
||||
foreach(IFilesystem fs in plugins.Filesystems.Values)
|
||||
{
|
||||
if(fs is not IReadOnlyFilesystem rofs)
|
||||
continue;
|
||||
|
||||
if(!fs.Identify(image, partition))
|
||||
continue;
|
||||
|
||||
if(rofs.Mount(image, partition, encoding, null, null) != ErrorNumber.NoError)
|
||||
continue;
|
||||
|
||||
try
|
||||
{
|
||||
foreach(string path in UnitKeyPaths)
|
||||
{
|
||||
if(rofs.OpenFile(path, out IFileNode? node) != ErrorNumber.NoError || node is null)
|
||||
continue;
|
||||
|
||||
try
|
||||
{
|
||||
if(node.Length <= 0 || node.Length > 10 * 1024 * 1024)
|
||||
continue;
|
||||
|
||||
byte[] buf = new byte[node.Length];
|
||||
|
||||
if(rofs.ReadFile(node, node.Length, buf, out long read) != ErrorNumber.NoError ||
|
||||
read != node.Length)
|
||||
continue;
|
||||
|
||||
fileBytes = buf;
|
||||
|
||||
return true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
rofs.CloseFile(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
rofs.Unmount();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
57
Aaru.Core/Image/Convert/AACS/ConvertAacsBd.cs
Normal file
57
Aaru.Core/Image/Convert/AACS/ConvertAacsBd.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Aaru.CommonTypes.Enums;
|
||||
using Aaru.CommonTypes.Interfaces;
|
||||
using Aaru.Localization;
|
||||
|
||||
namespace Aaru.Core.Image;
|
||||
|
||||
public partial class Convert
|
||||
{
|
||||
/// <summary>Blu-ray AACS: sector-aligned CPS units, staging, and per-sector <see cref="SectorStatus" />.</summary>
|
||||
ErrorNumber ConvertAacsBdOpticalSectors(IOpticalMediaImage inputOptical, IWritableOpticalImage outputOptical)
|
||||
{
|
||||
ErrorNumber errno = AacsBdExtentResolver.ResolveStreamFileExtents(inputOptical,
|
||||
_plugins,
|
||||
out List<AacsBdExtentResolver.LbaRange> ranges,
|
||||
out string err);
|
||||
|
||||
Func<ulong, bool> allowDecryptLba = null;
|
||||
|
||||
if(errno == ErrorNumber.NoError)
|
||||
allowDecryptLba = lba => AacsBdExtentResolver.IsLbaAllowed(ranges, lba);
|
||||
else if(_force)
|
||||
ErrorMessage?.Invoke(err + " Continuing with broad Blu-ray AACS decrypt scope.");
|
||||
else
|
||||
{
|
||||
StoppingErrorMessage?.Invoke(err);
|
||||
|
||||
return errno;
|
||||
}
|
||||
|
||||
return AacsBdOpticalPipeline.Run(inputOptical,
|
||||
outputOptical,
|
||||
_count,
|
||||
_force,
|
||||
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.Converting_sectors_in_track_0_of_1, i + 1, n), i, n),
|
||||
OnSectorRangeProgress = (start, end, seq, done, tot) =>
|
||||
UpdateProgress2?.Invoke(string.Format(UI.Converting_sectors_0_to_1_in_track_2,
|
||||
start,
|
||||
end,
|
||||
seq),
|
||||
done,
|
||||
tot),
|
||||
ErrorMessage = msg => ErrorMessage?.Invoke(msg),
|
||||
StoppingErrorMessage = msg => StoppingErrorMessage?.Invoke(msg)
|
||||
});
|
||||
}
|
||||
}
|
||||
203
Aaru.Core/Image/Convert/CSS/ConvertCssDvd.cs
Normal file
203
Aaru.Core/Image/Convert/CSS/ConvertCssDvd.cs
Normal file
@@ -0,0 +1,203 @@
|
||||
using Aaru.CommonTypes.Enums;
|
||||
using Aaru.CommonTypes.Interfaces;
|
||||
using Aaru.CommonTypes.Structs;
|
||||
using Aaru.Localization;
|
||||
|
||||
namespace Aaru.Core.Image;
|
||||
|
||||
public partial class Convert
|
||||
{
|
||||
/// <summary>Converts CSS-encrypted DVD optical sectors.</summary>
|
||||
/// <param name="inputOptical">Input optical media image.</param>
|
||||
/// <param name="outputOptical">Output optical media image.</param>
|
||||
/// <param name="useLong">True to use long sector reads.</param>
|
||||
/// <returns>Error number.</returns>
|
||||
ErrorNumber ConvertCssDvdOpticalSectors(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.Converting_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 = trackSectors - doneSectors >= _count ? _count : (uint)(trackSectors - doneSectors);
|
||||
|
||||
UpdateProgress2?.Invoke(string.Format(UI.Converting_sectors_0_to_1_in_track_2,
|
||||
doneSectors + track.StartSector,
|
||||
doneSectors + sectorsToDo + track.StartSector,
|
||||
track.Sequence),
|
||||
(long)doneSectors,
|
||||
(long)trackSectors);
|
||||
|
||||
bool result = false;
|
||||
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
|
||||
{
|
||||
result = true;
|
||||
|
||||
if(_force)
|
||||
{
|
||||
ErrorMessage?.Invoke(string.Format(UI.Error_0_reading_sector_1_continuing,
|
||||
errno,
|
||||
doneSectors + track.StartSector));
|
||||
}
|
||||
else
|
||||
{
|
||||
StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_reading_sector_1_not_continuing,
|
||||
errno,
|
||||
doneSectors + track.StartSector));
|
||||
|
||||
return ErrorNumber.WriteError;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
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
|
||||
{
|
||||
result = true;
|
||||
|
||||
if(_force)
|
||||
{
|
||||
ErrorMessage?.Invoke(string.Format(UI.Error_0_reading_sector_1_continuing,
|
||||
errno,
|
||||
doneSectors + track.StartSector));
|
||||
}
|
||||
else
|
||||
{
|
||||
StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_reading_sector_1_not_continuing,
|
||||
errno,
|
||||
doneSectors + track.StartSector));
|
||||
|
||||
return ErrorNumber.WriteError;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!result)
|
||||
{
|
||||
if(_force)
|
||||
{
|
||||
ErrorMessage?.Invoke(string.Format(UI.Error_0_writing_sector_1_continuing,
|
||||
outputOptical.ErrorMessage,
|
||||
doneSectors + track.StartSector));
|
||||
}
|
||||
else
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
415
Aaru.Core/Image/Convert/CSS/CssDvdSectorDecrypt.cs
Normal file
415
Aaru.Core/Image/Convert/CSS/CssDvdSectorDecrypt.cs
Normal file
@@ -0,0 +1,415 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Aaru.CommonTypes;
|
||||
using Aaru.CommonTypes.Enums;
|
||||
using Aaru.CommonTypes.Interfaces;
|
||||
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;
|
||||
|
||||
/// <summary>CSS decryption and <see cref="SectorStatus"/> tagging for DVD optical pipelines.</summary>
|
||||
static class CssDvdSectorDecrypt
|
||||
{
|
||||
internal static bool IsDvdMedia(MediaType mediaType) =>
|
||||
mediaType is MediaType.DVDROM
|
||||
or MediaType.DVDR
|
||||
or MediaType.DVDRW
|
||||
or MediaType.DVDPR
|
||||
or MediaType.DVDPRW
|
||||
or MediaType.DVDPRWDL
|
||||
or MediaType.DVDRDL
|
||||
or MediaType.DVDPRDL
|
||||
or MediaType.DVDRAM
|
||||
or MediaType.DVDRWDL
|
||||
or MediaType.DVDDownload
|
||||
or MediaType.PS2DVD
|
||||
or MediaType.PS3DVD
|
||||
or MediaType.XGD
|
||||
or MediaType.XGD2
|
||||
or MediaType.XGD3
|
||||
or MediaType.Nuon;
|
||||
|
||||
/// <summary>Generates DVD title keys.</summary>
|
||||
/// <param name="inputOptical">Input optical media image.</param>
|
||||
/// <param name="plugins">Plugin register.</param>
|
||||
/// <param name="generatedTitleKeys">Generated title keys.</param>
|
||||
/// <param name="logModuleName">Log module name.</param>
|
||||
/// <param name="isAborted">Is aborted function.</param>
|
||||
internal static void GenerateDvdTitleKeys(IOpticalMediaImage inputOptical, PluginRegister plugins,
|
||||
ref byte[] generatedTitleKeys, string logModuleName,
|
||||
Func<bool> isAborted)
|
||||
{
|
||||
if(isAborted()) return;
|
||||
|
||||
List<Partition> allPartitions = Partitions.GetAll(inputOptical);
|
||||
|
||||
byte[] lastKeysFromVideoTsFilesystem = null;
|
||||
|
||||
foreach(IReadOnlyFilesystem rofs in plugins.ReadOnlyFilesystems.Values)
|
||||
{
|
||||
if(isAborted()) return;
|
||||
|
||||
List<Partition> supportedPartitions = [];
|
||||
|
||||
foreach(Partition partition in allPartitions)
|
||||
{
|
||||
if(!rofs.Identify(inputOptical, partition))
|
||||
continue;
|
||||
|
||||
supportedPartitions.Add(partition);
|
||||
}
|
||||
|
||||
if(supportedPartitions.Count == 0)
|
||||
continue;
|
||||
|
||||
bool hasVideoTs = false;
|
||||
|
||||
foreach(Partition partition in supportedPartitions)
|
||||
{
|
||||
if(CSS.HasVideoTsFolder(inputOptical, rofs, partition))
|
||||
{
|
||||
hasVideoTs = true;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!hasVideoTs)
|
||||
continue;
|
||||
|
||||
AaruLogging.Debug(logModuleName, UI.Generating_decryption_keys);
|
||||
|
||||
byte[] keys = CSS.GenerateTitleKeys(inputOptical,
|
||||
supportedPartitions,
|
||||
inputOptical.Info.Sectors,
|
||||
rofs);
|
||||
|
||||
lastKeysFromVideoTsFilesystem = keys;
|
||||
|
||||
if(!TitleKeysBufferIsAllZero(keys))
|
||||
{
|
||||
generatedTitleKeys = keys;
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if(lastKeysFromVideoTsFilesystem != null)
|
||||
generatedTitleKeys = lastKeysFromVideoTsFilesystem;
|
||||
}
|
||||
|
||||
/// <summary>Checks if the title keys buffer is all zero.</summary>
|
||||
/// <param name="keys">Title keys buffer.</param>
|
||||
/// <returns>True if the title keys buffer is all zero.</returns>
|
||||
static bool TitleKeysBufferIsAllZero(byte[] keys)
|
||||
{
|
||||
if(keys is null || keys.Length == 0)
|
||||
return true;
|
||||
|
||||
for(int i = 0; i < keys.Length; i++)
|
||||
{
|
||||
if(keys[i] != 0)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>Applies CSS after reading a sector.</summary>
|
||||
/// <param name="sector">Sector data.</param>
|
||||
/// <param name="sectorStatus">Sector status.</param>
|
||||
/// <param name="sectorStatusArray">Sector status array.</param>
|
||||
/// <param name="sectorsToDo">Number of sectors to do.</param>
|
||||
/// <param name="readOneSector">True if reading one sector.</param>
|
||||
/// <param name="inputOptical">Input optical media image.</param>
|
||||
/// <param name="sectorAddress">Sector address.</param>
|
||||
/// <param name="plugins">Plugin register.</param>
|
||||
/// <param name="generatedTitleKeys">Generated title keys.</param>
|
||||
/// <param name="isAborted">Is aborted function.</param>
|
||||
/// <param name="logModuleName">Log module name.</param>
|
||||
internal static void ApplyCssAfterRead(ref byte[] sector, ref SectorStatus sectorStatus,
|
||||
SectorStatus[] sectorStatusArray, uint sectorsToDo, bool readOneSector,
|
||||
IOpticalMediaImage inputOptical, ulong sectorAddress,
|
||||
PluginRegister plugins, ref byte[] generatedTitleKeys,
|
||||
Func<bool> isAborted, string logModuleName)
|
||||
{
|
||||
if(isAborted()) return;
|
||||
|
||||
int blockSize = sector.Length / (int)sectorsToDo;
|
||||
|
||||
if(sector.Length % sectorsToDo != 0 || blockSize <= 0)
|
||||
return;
|
||||
|
||||
if(!Mpeg.ContainsMpegPackets(sector, sectorsToDo, (uint)blockSize))
|
||||
{
|
||||
SetStatusesDumped(ref sectorStatus, sectorStatusArray, sectorsToDo, readOneSector);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
bool[] blockAppliedCssDecrypt = new bool[sectorsToDo];
|
||||
|
||||
DecryptDvdSector(ref sector,
|
||||
inputOptical,
|
||||
sectorAddress,
|
||||
sectorsToDo,
|
||||
plugins,
|
||||
ref generatedTitleKeys,
|
||||
isAborted,
|
||||
logModuleName,
|
||||
blockAppliedCssDecrypt);
|
||||
|
||||
ApplyDecryptFlagsToStatuses(blockAppliedCssDecrypt,
|
||||
sectorsToDo,
|
||||
ref sectorStatus,
|
||||
sectorStatusArray,
|
||||
readOneSector);
|
||||
}
|
||||
|
||||
/// <summary>Applies CSS after reading a long sector.</summary>
|
||||
/// <param name="sector">Sector data.</param>
|
||||
/// <param name="sectorStatus">Sector status.</param>
|
||||
/// <param name="sectorStatusArray">Sector status array.</param>
|
||||
/// <param name="sectorsToDo">Number of sectors to do.</param>
|
||||
/// <param name="readOneSector">True if reading one sector.</param>
|
||||
/// <param name="inputOptical">Input optical media image.</param>
|
||||
/// <param name="sectorAddress">Sector address.</param>
|
||||
/// <param name="plugins">Plugin register.</param>
|
||||
/// <param name="generatedTitleKeys">Generated title keys.</param>
|
||||
/// <param name="isAborted">Is aborted function.</param>
|
||||
/// <param name="logModuleName">Log module name.</param>
|
||||
internal static void ApplyCssAfterReadLong(ref byte[] sector, ref SectorStatus sectorStatus,
|
||||
SectorStatus[] sectorStatusArray, uint sectorsToDo, bool readOneSector,
|
||||
IOpticalMediaImage inputOptical, ulong sectorAddress,
|
||||
PluginRegister plugins, ref byte[] generatedTitleKeys,
|
||||
Func<bool> isAborted, string logModuleName)
|
||||
{
|
||||
if(isAborted()) return;
|
||||
|
||||
if((long)sector.Length != (long)sectorsToDo * Mpeg.DvdLongSectorStride)
|
||||
return;
|
||||
|
||||
if(!Mpeg.ContainsMpegPacketsLong(sector, sectorsToDo))
|
||||
{
|
||||
SetStatusesDumped(ref sectorStatus, sectorStatusArray, sectorsToDo, readOneSector);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
bool[] blockAppliedCssDecrypt = new bool[sectorsToDo];
|
||||
|
||||
DecryptDvdSectorLong(ref sector,
|
||||
inputOptical,
|
||||
sectorAddress,
|
||||
sectorsToDo,
|
||||
plugins,
|
||||
ref generatedTitleKeys,
|
||||
isAborted,
|
||||
logModuleName,
|
||||
blockAppliedCssDecrypt);
|
||||
|
||||
ApplyDecryptFlagsToStatuses(blockAppliedCssDecrypt,
|
||||
sectorsToDo,
|
||||
ref sectorStatus,
|
||||
sectorStatusArray,
|
||||
readOneSector);
|
||||
}
|
||||
|
||||
/// <summary>Sets all the statuses to dumped.</summary>
|
||||
/// <param name="sectorStatus">Sector status.</param>
|
||||
/// <param name="sectorStatusArray">Sector status array.</param>
|
||||
/// <param name="sectorsToDo">Number of sectors to do.</param>
|
||||
/// <param name="readOneSector">True if reading one sector.</param>
|
||||
static void SetStatusesDumped(ref SectorStatus sectorStatus, SectorStatus[] sectorStatusArray, uint sectorsToDo,
|
||||
bool readOneSector)
|
||||
{
|
||||
if(readOneSector)
|
||||
sectorStatus = SectorStatus.Dumped;
|
||||
else
|
||||
{
|
||||
for(uint i = 0; i < sectorsToDo; i++)
|
||||
sectorStatusArray[i] = SectorStatus.Dumped;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Applies the decrypt flags to the statuses.</summary>
|
||||
/// <param name="blockAppliedCssDecrypt">Block applied CSS decrypt.</param>
|
||||
/// <param name="sectorsToDo">Number of sectors to do.</param>
|
||||
/// <param name="sectorStatus">Sector status.</param>
|
||||
/// <param name="sectorStatusArray">Sector status array.</param>
|
||||
/// <param name="readOneSector">True if reading one sector.</param>
|
||||
static void ApplyDecryptFlagsToStatuses(bool[] blockAppliedCssDecrypt, uint sectorsToDo,
|
||||
ref SectorStatus sectorStatus, SectorStatus[] sectorStatusArray,
|
||||
bool readOneSector)
|
||||
{
|
||||
if(readOneSector)
|
||||
sectorStatus = blockAppliedCssDecrypt[0] ? SectorStatus.Unencrypted : SectorStatus.Dumped;
|
||||
else
|
||||
{
|
||||
for(uint i = 0; i < sectorsToDo; i++)
|
||||
sectorStatusArray[i] = blockAppliedCssDecrypt[i] ? SectorStatus.Unencrypted : SectorStatus.Dumped;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Decrypts a DVD sector.</summary>
|
||||
/// <param name="sector">Sector data.</param>
|
||||
/// <param name="inputOptical">Input optical media image.</param>
|
||||
/// <param name="sectorAddress">Sector address.</param>
|
||||
/// <param name="sectorsToDo">Number of sectors to do.</param>
|
||||
/// <param name="plugins">Plugin register.</param>
|
||||
/// <param name="generatedTitleKeys">Generated title keys.</param>
|
||||
/// <param name="isAborted">Is aborted function.</param>
|
||||
/// <param name="logModuleName">Log module name.</param>
|
||||
/// <param name="blockAppliedCssDecrypt">Block applied CSS decrypt.</param>
|
||||
static void DecryptDvdSector(ref byte[] sector, IOpticalMediaImage inputOptical, ulong sectorAddress,
|
||||
uint sectorsToDo, PluginRegister plugins, ref byte[] generatedTitleKeys,
|
||||
Func<bool> isAborted, string logModuleName, bool[] blockAppliedCssDecrypt)
|
||||
{
|
||||
if(isAborted()) return;
|
||||
|
||||
int blockSize = sector.Length / (int)sectorsToDo;
|
||||
|
||||
if(sector.Length % sectorsToDo != 0 || blockSize <= 0)
|
||||
return;
|
||||
|
||||
if(!Mpeg.ContainsMpegPackets(sector, sectorsToDo, (uint)blockSize)) 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, 1, (uint)blockSize, blockAppliedCssDecrypt);
|
||||
else
|
||||
{
|
||||
if(generatedTitleKeys == null)
|
||||
GenerateDvdTitleKeys(inputOptical, plugins, ref generatedTitleKeys, logModuleName, isAborted);
|
||||
|
||||
if(generatedTitleKeys != null)
|
||||
{
|
||||
sector = CSS.DecryptSector(sector,
|
||||
generatedTitleKeys.Skip((int)(5 * sectorAddress)).Take(5).ToArray(),
|
||||
null,
|
||||
1,
|
||||
(uint)blockSize,
|
||||
blockAppliedCssDecrypt);
|
||||
}
|
||||
}
|
||||
}
|
||||
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, (uint)blockSize, blockAppliedCssDecrypt);
|
||||
else
|
||||
{
|
||||
if(generatedTitleKeys == null)
|
||||
GenerateDvdTitleKeys(inputOptical, plugins, ref generatedTitleKeys, logModuleName, isAborted);
|
||||
|
||||
if(generatedTitleKeys != null)
|
||||
{
|
||||
sector = CSS.DecryptSector(sector,
|
||||
generatedTitleKeys.Skip((int)(5 * sectorAddress))
|
||||
.Take((int)(5 * sectorsToDo))
|
||||
.ToArray(),
|
||||
null,
|
||||
sectorsToDo,
|
||||
(uint)blockSize,
|
||||
blockAppliedCssDecrypt);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Decrypts a DVD long sector.</summary>
|
||||
/// <param name="sector">Sector data.</param>
|
||||
/// <param name="inputOptical">Input optical media image.</param>
|
||||
/// <param name="sectorAddress">Sector address.</param>
|
||||
/// <param name="sectorsToDo">Number of sectors to do.</param>
|
||||
/// <param name="plugins">Plugin register.</param>
|
||||
/// <param name="generatedTitleKeys">Generated title keys.</param>
|
||||
/// <param name="isAborted">Is aborted function.</param>
|
||||
/// <param name="logModuleName">Log module name.</param>
|
||||
/// <param name="blockAppliedCssDecrypt">Block applied CSS decrypt.</param>
|
||||
static void DecryptDvdSectorLong(ref byte[] sector, IOpticalMediaImage inputOptical, ulong sectorAddress,
|
||||
uint sectorsToDo, PluginRegister plugins, ref byte[] generatedTitleKeys,
|
||||
Func<bool> isAborted, string logModuleName, bool[] blockAppliedCssDecrypt)
|
||||
{
|
||||
if(isAborted()) return;
|
||||
|
||||
if((long)sector.Length != (long)sectorsToDo * Mpeg.DvdLongSectorStride)
|
||||
return;
|
||||
|
||||
if(!Mpeg.ContainsMpegPacketsLong(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.DecryptSectorLong(sector, titleKey, cmi, 1, 2048, blockAppliedCssDecrypt);
|
||||
else
|
||||
{
|
||||
if(generatedTitleKeys == null)
|
||||
GenerateDvdTitleKeys(inputOptical, plugins, ref generatedTitleKeys, logModuleName, isAborted);
|
||||
|
||||
if(generatedTitleKeys != null)
|
||||
{
|
||||
sector = CSS.DecryptSectorLong(sector,
|
||||
generatedTitleKeys.Skip((int)(5 * sectorAddress)).Take(5).ToArray(),
|
||||
null,
|
||||
1,
|
||||
2048,
|
||||
blockAppliedCssDecrypt);
|
||||
}
|
||||
}
|
||||
}
|
||||
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.DecryptSectorLong(sector, titleKey, cmi, sectorsToDo, 2048, blockAppliedCssDecrypt);
|
||||
else
|
||||
{
|
||||
if(generatedTitleKeys == null)
|
||||
GenerateDvdTitleKeys(inputOptical, plugins, ref generatedTitleKeys, logModuleName, isAborted);
|
||||
|
||||
if(generatedTitleKeys != null)
|
||||
{
|
||||
sector = CSS.DecryptSectorLong(sector,
|
||||
generatedTitleKeys.Skip((int)(5 * sectorAddress))
|
||||
.Take((int)(5 * sectorsToDo))
|
||||
.ToArray(),
|
||||
null,
|
||||
sectorsToDo,
|
||||
2048,
|
||||
blockAppliedCssDecrypt);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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) =>
|
||||
|
||||
|
||||
57
Aaru.Core/Image/Merge/MergeAacsBd.cs
Normal file
57
Aaru.Core/Image/Merge/MergeAacsBd.cs
Normal file
@@ -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
|
||||
{
|
||||
/// <summary>Merges Blu-ray AACS optical sectors.</summary>
|
||||
/// <param name="inputOptical">Input optical media image.</param>
|
||||
/// <param name="outputOptical">Output optical media image.</param>
|
||||
/// <returns>Error number.</returns>
|
||||
ErrorNumber CopyAacsBdOpticalSectorsPrimary(IOpticalMediaImage inputOptical, IWritableOpticalImage outputOptical)
|
||||
{
|
||||
ErrorNumber errno = AacsBdExtentResolver.ResolveStreamFileExtents(inputOptical,
|
||||
_plugins,
|
||||
out List<AacsBdExtentResolver.LbaRange> ranges,
|
||||
out string err);
|
||||
|
||||
if(errno != ErrorNumber.NoError)
|
||||
{
|
||||
StoppingErrorMessage?.Invoke(err);
|
||||
|
||||
return errno;
|
||||
}
|
||||
|
||||
Func<ulong, bool> 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)
|
||||
});
|
||||
}
|
||||
}
|
||||
297
Aaru.Core/Image/Merge/MergeCssDvd.cs
Normal file
297
Aaru.Core/Image/Merge/MergeCssDvd.cs
Normal file
@@ -0,0 +1,297 @@
|
||||
// /***************************************************************************
|
||||
// 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
|
||||
{
|
||||
/// <summary>Merges CSS-encrypted DVD optical sectors.</summary>
|
||||
/// <param name="inputOptical">Input optical media image.</param>
|
||||
/// <param name="outputOptical">Output optical media image.</param>
|
||||
/// <param name="useLong">True to use long sector reads.</param>
|
||||
/// <returns>Error number.</returns>
|
||||
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 = trackSectors - doneSectors >= (ulong)count
|
||||
? (uint)count
|
||||
: (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;
|
||||
}
|
||||
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>Merges CSS-encrypted DVD optical sectors from a secondary image.</summary>
|
||||
/// <param name="inputOptical">Input optical media image.</param>
|
||||
/// <param name="outputOptical">Output optical media image.</param>
|
||||
/// <param name="useLong">True to use long sector reads.</param>
|
||||
/// <param name="sectorsToCopy">Sectors to copy.</param>
|
||||
/// <returns>Error number.</returns>
|
||||
ErrorNumber CopyCssDvdOpticalSectorsSecondary(IOpticalMediaImage inputOptical, IWritableOpticalImage outputOptical,
|
||||
bool useLong, List<ulong> 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))
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -57,6 +57,7 @@ public sealed partial class Merger
|
||||
const string MODULE_NAME = "Image merger";
|
||||
bool _aborted;
|
||||
readonly PluginRegister _plugins = PluginRegister.Singleton;
|
||||
byte[][]? _aacsDecryptedCpsUnitKeys;
|
||||
|
||||
public ErrorNumber Start()
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/// <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
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <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
|
||||
/// </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);
|
||||
}
|
||||
|
||||
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) =>
|
||||
|
||||
|
||||
@@ -434,4 +434,14 @@ public sealed class Sector
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
|
||||
public static byte[] GetUserData(byte[] data)
|
||||
{
|
||||
if(data.Length != 2064) return data;
|
||||
|
||||
byte[] sector = new byte[2048];
|
||||
Array.Copy(data, 12, sector, 0, 2048);
|
||||
|
||||
return sector;
|
||||
}
|
||||
}
|
||||
125
Aaru.Decryption/Aacs/AacsConvertBuffer.cs
Normal file
125
Aaru.Decryption/Aacs/AacsConvertBuffer.cs
Normal file
@@ -0,0 +1,125 @@
|
||||
// /***************************************************************************
|
||||
// Aaru Data Preservation Suite
|
||||
// ----------------------------------------------------------------------------
|
||||
//
|
||||
// Filename : AacsConvertBuffer.cs
|
||||
// Author(s) : Rebecca Wallander <sakcheen@gmail.com>
|
||||
//
|
||||
// --[ Description ] ----------------------------------------------------------
|
||||
//
|
||||
// Buffers 2048-byte sectors into 6144-byte AACS CPS units across reads.
|
||||
//
|
||||
// --[ 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 © 2026 Rebecca Wallander
|
||||
// ****************************************************************************/
|
||||
|
||||
using System;
|
||||
|
||||
namespace Aaru.Decryption.Aacs;
|
||||
|
||||
/// <summary>Aligns contiguous 2048-byte sectors to CPS units for decrypt during image conversion.</summary>
|
||||
public sealed class AacsConvertBuffer
|
||||
{
|
||||
byte[] _pending = Array.Empty<byte>();
|
||||
/// <summary>LBA of the first 2048-byte sector in <see cref="_pending"/>.</summary>
|
||||
ulong _pendingStartLba;
|
||||
|
||||
/// <summary>Clears any trailing sectors (e.g. at end of conversion).</summary>
|
||||
public void Reset()
|
||||
{
|
||||
_pending = Array.Empty<byte>();
|
||||
_pendingStartLba = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prepends pending data to <paramref name="sectorBuffer"/> (first <paramref name="sectorCount"/> sectors),
|
||||
/// decrypts all complete 6144-byte units, writes decrypted bytes back into <paramref name="sectorBuffer"/>,
|
||||
/// and stores an incomplete trailing 0–2 sectors for the next call.
|
||||
/// </summary>
|
||||
/// <param name="sectorBuffer">Sector buffer to decrypt.</param>
|
||||
/// <param name="sectorCount">Number of sectors to decrypt.</param>
|
||||
/// <param name="firstSectorLba">LBA of the first sector.</param>
|
||||
/// <param name="decryptedCpsUnitKeys">Decrypted CPS unit keys.</param>
|
||||
public void DecryptChunk(ref byte[] sectorBuffer, uint sectorCount, ulong firstSectorLba,
|
||||
byte[][] decryptedCpsUnitKeys)
|
||||
{
|
||||
if(sectorCount == 0)
|
||||
return;
|
||||
|
||||
int newBytes = (int)(sectorCount * AacsStreamDecrypt.SectorLen);
|
||||
|
||||
if(sectorBuffer.Length < newBytes)
|
||||
throw new ArgumentException("sectorBuffer shorter than sectorCount implies.", nameof(sectorBuffer));
|
||||
|
||||
int pl = _pending.Length;
|
||||
|
||||
if(pl > 0)
|
||||
{
|
||||
ulong expected = _pendingStartLba + (ulong)(pl / AacsStreamDecrypt.SectorLen);
|
||||
|
||||
if(expected != firstSectorLba)
|
||||
{
|
||||
_pending = Array.Empty<byte>();
|
||||
_pendingStartLba = 0;
|
||||
pl = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if(pl == 0)
|
||||
_pendingStartLba = firstSectorLba;
|
||||
|
||||
int totalLen = pl + newBytes;
|
||||
byte[] merged = new byte[totalLen];
|
||||
|
||||
if(pl > 0)
|
||||
Buffer.BlockCopy(_pending, 0, merged, 0, pl);
|
||||
|
||||
Buffer.BlockCopy(sectorBuffer, 0, merged, pl, newBytes);
|
||||
|
||||
int fullUnitsBytes = totalLen / AacsStreamDecrypt.AlignedUnitLen * AacsStreamDecrypt.AlignedUnitLen;
|
||||
|
||||
Span<byte> mergedSpan = merged;
|
||||
|
||||
for(int o = 0; o < fullUnitsBytes; o += AacsStreamDecrypt.AlignedUnitLen)
|
||||
{
|
||||
Span<byte> unit = mergedSpan.Slice(o, AacsStreamDecrypt.AlignedUnitLen);
|
||||
AacsStreamDecrypt.TryDecryptAlignedUnit(unit, decryptedCpsUnitKeys);
|
||||
}
|
||||
|
||||
int rem = totalLen - fullUnitsBytes;
|
||||
|
||||
if(rem > 0)
|
||||
{
|
||||
_pending = new byte[rem];
|
||||
Buffer.BlockCopy(merged, fullUnitsBytes, _pending, 0, rem);
|
||||
}
|
||||
else
|
||||
{
|
||||
_pending = Array.Empty<byte>();
|
||||
_pendingStartLba = 0;
|
||||
}
|
||||
|
||||
Buffer.BlockCopy(merged, pl, sectorBuffer, 0, newBytes);
|
||||
}
|
||||
}
|
||||
165
Aaru.Decryption/Aacs/AacsCrypto.cs
Normal file
165
Aaru.Decryption/Aacs/AacsCrypto.cs
Normal file
@@ -0,0 +1,165 @@
|
||||
// /***************************************************************************
|
||||
// Aaru Data Preservation Suite
|
||||
// ----------------------------------------------------------------------------
|
||||
//
|
||||
// Filename : AacsCrypto.cs
|
||||
// Author(s) : Rebecca Wallander <sakcheen@gmail.com>
|
||||
//
|
||||
// --[ Description ] ----------------------------------------------------------
|
||||
//
|
||||
// AES primitives for Blu-ray AACS
|
||||
//
|
||||
// --[ 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 © 2026 Rebecca Wallander
|
||||
// ****************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace Aaru.Decryption.Aacs;
|
||||
|
||||
/// <summary>Low-level AES operations used by Blu-ray AACS stream decryption.</summary>
|
||||
public static class AacsCrypto
|
||||
{
|
||||
/// <summary>AES-128 CBC IV.</summary>
|
||||
static readonly byte[] AacsCbcIv =
|
||||
[
|
||||
0x0b, 0xa0, 0xf8, 0xdd, 0xfe, 0xa6, 0x1f, 0xb3, 0xd8, 0xdf, 0x9f, 0x56, 0x6a, 0x05, 0x0f, 0x78
|
||||
];
|
||||
|
||||
/// <summary>AES-128 ECB encrypt one block.</summary>
|
||||
/// <param name="key">AES-128 key (16 bytes).</param>
|
||||
/// <param name="plaintext">Plaintext to encrypt (16 bytes).</param>
|
||||
/// <param name="ciphertext">Encrypted output (16 bytes).</param>
|
||||
public static void Aes128EcbEncrypt(ReadOnlySpan<byte> key, ReadOnlySpan<byte> plaintext, Span<byte> ciphertext)
|
||||
{
|
||||
if(key.Length != 16 || plaintext.Length != 16 || ciphertext.Length != 16)
|
||||
throw new ArgumentException("AES-128 block requires 16-byte key, plaintext, and output.");
|
||||
|
||||
byte[] keyArr = key.ToArray();
|
||||
byte[] ptArr = plaintext.ToArray();
|
||||
|
||||
using(Aes aes = Aes.Create())
|
||||
{
|
||||
aes.KeySize = 128;
|
||||
aes.Mode = CipherMode.ECB;
|
||||
aes.Padding = PaddingMode.None;
|
||||
aes.Key = keyArr;
|
||||
|
||||
using ICryptoTransform enc = aes.CreateEncryptor();
|
||||
byte[] ctArr = enc.TransformFinalBlock(ptArr, 0, 16);
|
||||
ctArr.AsSpan(0, 16).CopyTo(ciphertext);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>AES-128 ECB decrypt one block.</summary>
|
||||
/// <param name="key">AES-128 key (16 bytes).</param>
|
||||
/// <param name="ciphertext">Ciphertext to decrypt (16 bytes).</param>
|
||||
/// <param name="plaintext">Decrypted output (16 bytes).</param>
|
||||
public static void Aes128EcbDecrypt(ReadOnlySpan<byte> key, ReadOnlySpan<byte> ciphertext, Span<byte> plaintext)
|
||||
{
|
||||
if(key.Length != 16 || ciphertext.Length != 16 || plaintext.Length != 16)
|
||||
throw new ArgumentException("AES-128 block requires 16-byte key, ciphertext, and output.");
|
||||
|
||||
byte[] keyArr = key.ToArray();
|
||||
byte[] ctArr = ciphertext.ToArray();
|
||||
|
||||
using var aes = Aes.Create();
|
||||
aes.KeySize = 128;
|
||||
aes.Mode = CipherMode.ECB;
|
||||
aes.Padding = PaddingMode.None;
|
||||
aes.Key = keyArr;
|
||||
|
||||
using ICryptoTransform dec = aes.CreateDecryptor();
|
||||
byte[] ptArr = dec.TransformFinalBlock(ctArr, 0, 16);
|
||||
ptArr.AsSpan(0, 16).CopyTo(plaintext);
|
||||
}
|
||||
|
||||
/// <summary>AES-128 CBC decrypt.</summary>
|
||||
/// <param name="key">AES-128 key (16 bytes).</param>
|
||||
/// <param name="ciphertext">Ciphertext to decrypt (16 bytes).</param>
|
||||
/// <param name="plaintext">Decrypted output (16 bytes).</param>
|
||||
public static void AacsCbcDecrypt(ReadOnlySpan<byte> key, ReadOnlySpan<byte> ciphertext, Span<byte> plaintext)
|
||||
{
|
||||
if(key.Length != 16)
|
||||
throw new ArgumentException("AES-128 key must be 16 bytes.");
|
||||
|
||||
if(ciphertext.Length != plaintext.Length || ciphertext.Length == 0)
|
||||
throw new ArgumentException("Ciphertext and plaintext spans must have the same non-zero length.");
|
||||
|
||||
if((ciphertext.Length & 15) != 0)
|
||||
throw new ArgumentException("Ciphertext length must be a multiple of 16.");
|
||||
|
||||
byte[] keyArr = key.ToArray();
|
||||
byte[] ctArr = ciphertext.ToArray();
|
||||
byte[] ivArr = (byte[])AacsCbcIv.Clone();
|
||||
|
||||
using var aes = Aes.Create();
|
||||
aes.KeySize = 128;
|
||||
aes.Mode = CipherMode.CBC;
|
||||
aes.Padding = PaddingMode.None;
|
||||
aes.Key = keyArr;
|
||||
aes.IV = ivArr;
|
||||
|
||||
using ICryptoTransform dec = aes.CreateDecryptor();
|
||||
byte[] ptArr = dec.TransformFinalBlock(ctArr, 0, ctArr.Length);
|
||||
|
||||
if(ptArr.Length != plaintext.Length)
|
||||
throw new InvalidOperationException("AACS CBC decrypt length mismatch.");
|
||||
|
||||
ptArr.AsSpan().CopyTo(plaintext);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Volume unique key from media key and volume ID.
|
||||
/// </summary>
|
||||
/// <param name="mediaKey">Media key (16 bytes).</param>
|
||||
/// <param name="volumeId">Volume ID (16 bytes).</param>
|
||||
/// <param name="volumeUniqueKey">Volume unique key (16 bytes).</param>
|
||||
public static void DeriveVolumeUniqueKey(ReadOnlySpan<byte> mediaKey, ReadOnlySpan<byte> volumeId,
|
||||
Span<byte> volumeUniqueKey)
|
||||
{
|
||||
if(mediaKey.Length != 16 || volumeId.Length != 16 || volumeUniqueKey.Length != 16)
|
||||
throw new ArgumentException("MK, VID, and VUK must be 16 bytes.");
|
||||
|
||||
Span<byte> tmp = stackalloc byte[16];
|
||||
Aes128EcbDecrypt(mediaKey, volumeId, tmp);
|
||||
|
||||
for(int a = 0; a < 16; a++)
|
||||
volumeUniqueKey[a] = (byte)(tmp[a] ^ volumeId[a]);
|
||||
}
|
||||
|
||||
/// <summary>Decrypt one CPS unit key from encrypted form.</summary>
|
||||
/// <param name="volumeUniqueKey">Volume unique key (16 bytes).</param>
|
||||
/// <param name="encryptedUnitKey">Encrypted CPS unit key (16 bytes).</param>
|
||||
/// <param name="decryptedUnitKey">Decrypted CPS unit key (16 bytes).</param>
|
||||
public static void DecryptCpsUnitKey(ReadOnlySpan<byte> volumeUniqueKey, ReadOnlySpan<byte> encryptedUnitKey,
|
||||
Span<byte> decryptedUnitKey)
|
||||
{
|
||||
if(volumeUniqueKey.Length != 16 || encryptedUnitKey.Length != 16 || decryptedUnitKey.Length != 16)
|
||||
throw new ArgumentException("VUK and CPS unit keys must be 16 bytes.");
|
||||
|
||||
Aes128EcbDecrypt(volumeUniqueKey, encryptedUnitKey, decryptedUnitKey);
|
||||
}
|
||||
}
|
||||
135
Aaru.Decryption/Aacs/AacsStreamDecrypt.cs
Normal file
135
Aaru.Decryption/Aacs/AacsStreamDecrypt.cs
Normal file
@@ -0,0 +1,135 @@
|
||||
// /***************************************************************************
|
||||
// Aaru Data Preservation Suite
|
||||
// ----------------------------------------------------------------------------
|
||||
//
|
||||
// Filename : AacsStreamDecrypt.cs
|
||||
// Author(s) : Rebecca Wallander <sakcheen@gmail.com>
|
||||
//
|
||||
// --[ Description ] ----------------------------------------------------------
|
||||
//
|
||||
// Blu-ray AACS aligned-unit (6144-byte) decrypt.
|
||||
//
|
||||
// --[ 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 © 2026 Rebecca Wallander
|
||||
// ****************************************************************************/
|
||||
|
||||
using System;
|
||||
|
||||
namespace Aaru.Decryption.Aacs;
|
||||
|
||||
/// <summary>Decrypts one 6144-byte aligned unit using decrypted CPS unit keys.</summary>
|
||||
public static class AacsStreamDecrypt
|
||||
{
|
||||
/// <summary>User-data sector size and bus-encryption block size.</summary>
|
||||
public const int SectorLen = 2048;
|
||||
|
||||
/// <summary>Three user sectors: CPS aligned unit length.</summary>
|
||||
public const int AlignedUnitLen = 6144;
|
||||
|
||||
/// <summary>
|
||||
/// If the unit is marked unencrypted (MPEG-TS copy permission bits), returns <see langword="true"/>
|
||||
/// without changing the buffer. Otherwise tries each CPS unit key until MPEG-TS sync verifies.
|
||||
/// </summary>
|
||||
/// <param name="unit">Unit to decrypt.</param>
|
||||
/// <param name="decryptedCpsUnitKeys">Decrypted CPS unit keys.</param>
|
||||
/// <returns>True if the unit was decrypted successfully.</returns>
|
||||
public static bool TryDecryptAlignedUnit(Span<byte> unit, ReadOnlySpan<byte[]> decryptedCpsUnitKeys)
|
||||
{
|
||||
if(unit.Length != AlignedUnitLen)
|
||||
throw new ArgumentException($"Unit must be {AlignedUnitLen} bytes.", nameof(unit));
|
||||
|
||||
if(decryptedCpsUnitKeys.Length == 0)
|
||||
return false;
|
||||
|
||||
// If the unit is not encrypted, return true.
|
||||
if((unit[0] & 0xc0) == 0)
|
||||
return true;
|
||||
|
||||
byte[] work = new byte[AlignedUnitLen];
|
||||
|
||||
for(int i = 0; i < decryptedCpsUnitKeys.Length; i++)
|
||||
{
|
||||
unit.CopyTo(work);
|
||||
|
||||
if(!TryDecryptUnitWithKey(work, decryptedCpsUnitKeys[i]))
|
||||
continue;
|
||||
|
||||
work.CopyTo(unit);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>Decrypts one unit with a given CPS unit key.</summary>
|
||||
/// <param name="unit">Unit to decrypt.</param>
|
||||
/// <param name="decryptedCpsUnitKey">Decrypted CPS unit key.</param>
|
||||
/// <returns>True if the unit was decrypted successfully.</returns>
|
||||
public static bool TryDecryptUnitWithKey(Span<byte> unit, ReadOnlySpan<byte> decryptedCpsUnitKey)
|
||||
{
|
||||
if(unit.Length != AlignedUnitLen)
|
||||
throw new ArgumentException($"Unit must be {AlignedUnitLen} bytes.", nameof(unit));
|
||||
|
||||
if(decryptedCpsUnitKey.Length != 16)
|
||||
throw new ArgumentException("CPS unit key must be 16 bytes.", nameof(decryptedCpsUnitKey));
|
||||
|
||||
Span<byte> key = stackalloc byte[16];
|
||||
AacsCrypto.Aes128EcbEncrypt(decryptedCpsUnitKey, unit.Slice(0, 16), key);
|
||||
|
||||
for(int a = 0; a < 16; a++)
|
||||
key[a] ^= unit[a];
|
||||
|
||||
ReadOnlySpan<byte> cipher = unit.Slice(16, AlignedUnitLen - 16);
|
||||
Span<byte> plain = unit.Slice(16, AlignedUnitLen - 16);
|
||||
byte[] tmp = new byte[AlignedUnitLen - 16];
|
||||
AacsCrypto.AacsCbcDecrypt(key, cipher, tmp);
|
||||
tmp.CopyTo(plain);
|
||||
|
||||
return VerifyAndNormalizeTransportStream(unit);
|
||||
}
|
||||
|
||||
/// <summary>Verifies and normalizes the transport stream. Removes copy-permission indicator bits.</summary>
|
||||
/// <param name="unit">Unit to verify and normalize.</param>
|
||||
/// <returns>True if the unit was verified and normalized successfully.</returns>
|
||||
public static bool VerifyAndNormalizeTransportStream(Span<byte> unit)
|
||||
{
|
||||
if(unit.Length != AlignedUnitLen)
|
||||
throw new ArgumentException($"Unit must be {AlignedUnitLen} bytes.", nameof(unit));
|
||||
|
||||
// In AACS aligned units, payload is expected to be 32 packets of 192 bytes.
|
||||
// Each 192-byte packet has a 4-byte TP extra header followed by a TS packet.
|
||||
for(int i = 0; i < AlignedUnitLen; i += 192)
|
||||
{
|
||||
// The TS sync byte (0x47) must be at offset +4 because of that extra 4-byte header.
|
||||
if(unit[i + 4] != 0x47)
|
||||
return false;
|
||||
|
||||
// Clear copy-permission indicator bits in the TP extra header.
|
||||
unit[i] &= 0x3f;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
65
Aaru.Decryption/Aacs/HDDVD.cs
Normal file
65
Aaru.Decryption/Aacs/HDDVD.cs
Normal file
@@ -0,0 +1,65 @@
|
||||
// /***************************************************************************
|
||||
// Aaru Data Preservation Suite
|
||||
// ----------------------------------------------------------------------------
|
||||
//
|
||||
// Filename : HDDVD.cs
|
||||
// Author(s) : Rebecca Wallander <sakcheen@gmail.com>
|
||||
//
|
||||
// --[ Description ] ----------------------------------------------------------
|
||||
//
|
||||
// HD DVD video disc helpers.
|
||||
//
|
||||
// --[ 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 © 2026 Rebecca Wallander
|
||||
// ****************************************************************************/
|
||||
|
||||
using Aaru.CommonTypes;
|
||||
using Aaru.CommonTypes.Enums;
|
||||
using Aaru.CommonTypes.Interfaces;
|
||||
using Aaru.CommonTypes.Structs;
|
||||
|
||||
namespace Aaru.Decryption.Aacs;
|
||||
|
||||
/// <summary>Aligns contiguous 2048-byte sectors to CPS units for decrypt during image conversion.</summary>
|
||||
public sealed class HDDVD
|
||||
{
|
||||
/// <summary>
|
||||
/// HD DVD video discs always have a <c>HVDVD_TS</c> folder. If it doesn't have one, it's not a HD DVD video.
|
||||
/// </summary>
|
||||
/// <param name="input"><c>IOpticalMediaImage</c> to check for <c>HVDVD_TS</c> folder in.</param>
|
||||
/// <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>HVDVD_TS</c> folder was found.</returns>
|
||||
public static bool HasHdDvdVideoTsFolder(IOpticalMediaImage input, IReadOnlyFilesystem fs, Partition partition)
|
||||
{
|
||||
ErrorNumber error = fs.Mount(input, partition, null, null, null);
|
||||
|
||||
if(error != ErrorNumber.NoError) return false;
|
||||
|
||||
error = fs.Stat("HVDVD_TS", out FileEntryInfo stat);
|
||||
fs.Unmount();
|
||||
|
||||
return error == ErrorNumber.NoError;
|
||||
}
|
||||
}
|
||||
162
Aaru.Decryption/Aacs/UnitKeyRoParser.cs
Normal file
162
Aaru.Decryption/Aacs/UnitKeyRoParser.cs
Normal file
@@ -0,0 +1,162 @@
|
||||
// /***************************************************************************
|
||||
// Aaru Data Preservation Suite
|
||||
// ----------------------------------------------------------------------------
|
||||
//
|
||||
// Filename : UnitKeyRoParser.cs
|
||||
// Author(s) : Rebecca Wallander <sakcheen@gmail.com>
|
||||
//
|
||||
// --[ Description ] ----------------------------------------------------------
|
||||
//
|
||||
// Parses AACS/Unit_Key_RO.inf
|
||||
//
|
||||
// --[ 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 © 2026 Rebecca Wallander
|
||||
// ****************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Buffers.Binary;
|
||||
|
||||
namespace Aaru.Decryption.Aacs;
|
||||
|
||||
/// <summary>Parsed CPS encrypted unit keys from <c>Unit_Key_RO.inf</c>.</summary>
|
||||
public sealed class UnitKeyRoParseResult
|
||||
{
|
||||
UnitKeyRoParseResult(byte[][] encryptedCpsUnitKeys, bool isAacs2Layout)
|
||||
{
|
||||
EncryptedCpsUnitKeys = encryptedCpsUnitKeys;
|
||||
IsAacs2Layout = isAacs2Layout;
|
||||
}
|
||||
|
||||
/// <summary>One 16-byte encrypted key per CPS unit, index order.</summary>
|
||||
public byte[][] EncryptedCpsUnitKeys { get; }
|
||||
|
||||
/// <summary>True when the AACS2 key stride (64 bytes per key entry) was used.</summary>
|
||||
public bool IsAacs2Layout { get; }
|
||||
|
||||
/// <summary>Parses Blu-ray <c>Unit_Key_RO.inf</c> (AACS1 layout by default).</summary>
|
||||
/// <param name="data">Data to parse.</param>
|
||||
/// <param name="tryAacs2">True to try AACS2 layout.</param>
|
||||
/// <returns>Parsed CPS encrypted unit keys.</returns>
|
||||
public static UnitKeyRoParseResult? TryParse(ReadOnlySpan<byte> data, bool tryAacs2 = false)
|
||||
{
|
||||
if(tryAacs2)
|
||||
return TryParseInternal(data, true);
|
||||
|
||||
UnitKeyRoParseResult? r = TryParseInternal(data, false);
|
||||
|
||||
return r ?? TryParseInternal(data, true);
|
||||
}
|
||||
|
||||
/// <summary>Parses Blu-ray <c>Unit_Key_RO.inf</c> (AACS1 layout by default).</summary>
|
||||
/// <param name="data">Data to parse.</param>
|
||||
/// <param name="aacs2">True to try AACS2 layout.</param>
|
||||
/// <returns>Parsed CPS encrypted unit keys.</returns>
|
||||
static UnitKeyRoParseResult? TryParseInternal(ReadOnlySpan<byte> data, bool aacs2)
|
||||
{
|
||||
if(data.Length < 20)
|
||||
return null;
|
||||
|
||||
byte numBdmvDir = data[17];
|
||||
|
||||
if(numBdmvDir < 1)
|
||||
return null;
|
||||
|
||||
if(data.Length < 4)
|
||||
return null;
|
||||
|
||||
uint ukPos = BinaryPrimitives.ReadUInt32BigEndian(data);
|
||||
|
||||
if(data.Length - 2 < ukPos)
|
||||
return null;
|
||||
|
||||
if((int)ukPos >= data.Length)
|
||||
return null;
|
||||
|
||||
ushort numUk = BinaryPrimitives.ReadUInt16BigEndian(data.Slice((int)ukPos, 2));
|
||||
|
||||
if(numUk < 1)
|
||||
return null;
|
||||
|
||||
if(data.Length - (int)ukPos < 16)
|
||||
return null;
|
||||
|
||||
if((data.Length - (int)ukPos - 16) / 48 < numUk)
|
||||
return null;
|
||||
|
||||
ReadOnlySpan<byte> emptyKey = stackalloc byte[16];
|
||||
|
||||
if(aacs2 && numUk > 1 && data.Length >= 48 + 48 + 16)
|
||||
{
|
||||
if(data.Slice(48 + 48 + 16, 16).SequenceEqual(emptyKey))
|
||||
aacs2 = false;
|
||||
else if(data.Length < (int)ukPos + 64 * numUk + 16)
|
||||
return null;
|
||||
}
|
||||
|
||||
byte[][] encKeys = new byte[numUk][];
|
||||
|
||||
uint pos = ukPos;
|
||||
|
||||
for(uint i = 0; i < numUk; i++)
|
||||
{
|
||||
pos += 48;
|
||||
|
||||
if(pos + 16 > (uint)data.Length)
|
||||
return null;
|
||||
|
||||
byte[] key = new byte[16];
|
||||
data.Slice((int)pos, 16).CopyTo(key);
|
||||
encKeys[i] = key;
|
||||
|
||||
if(aacs2)
|
||||
pos += 16;
|
||||
}
|
||||
|
||||
return new UnitKeyRoParseResult(encKeys, aacs2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interprets a tag or buffer as raw concatenated 16-byte encrypted CPS keys (no .inf wrapper).
|
||||
/// </summary>
|
||||
/// <param name="data">Data to parse.</param>
|
||||
/// <returns>Parsed CPS encrypted unit keys.</returns>
|
||||
public static UnitKeyRoParseResult? TryParseRawEncryptedKeys(ReadOnlySpan<byte> data)
|
||||
{
|
||||
if(data.Length < 16 || data.Length % 16 != 0)
|
||||
return null;
|
||||
|
||||
int n = data.Length / 16;
|
||||
byte[][] keys = new byte[n][];
|
||||
ReadOnlySpan<byte> p = data;
|
||||
|
||||
for(int i = 0; i < n; i++)
|
||||
{
|
||||
byte[] key = new byte[16];
|
||||
p.Slice(i * 16, 16).CopyTo(key);
|
||||
keys[i] = key;
|
||||
}
|
||||
|
||||
return new UnitKeyRoParseResult(keys, false);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -135,7 +135,9 @@ public sealed partial class ISO9660
|
||||
|
||||
if(_blockSize == 2048)
|
||||
{
|
||||
buffer = Sector.GetUserData(data, interleaved, fileNumber);
|
||||
buffer = data.Length == 2064
|
||||
? Decoders.DVD.Sector.GetUserData(data)
|
||||
: Sector.GetUserData(data, interleaved, fileNumber);
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
@@ -232,7 +234,9 @@ public sealed partial class ISO9660
|
||||
}
|
||||
}
|
||||
|
||||
byte[] sectorData = Sector.GetUserData(data, interleaved, fileNumber);
|
||||
byte[] sectorData = data.Length == 2064
|
||||
? Decoders.DVD.Sector.GetUserData(data)
|
||||
: Sector.GetUserData(data, interleaved, fileNumber);
|
||||
|
||||
ms.Write(sectorData, 0, sectorData.Length);
|
||||
}
|
||||
|
||||
157
Aaru.Filesystems/UDF/Extents.cs
Normal file
157
Aaru.Filesystems/UDF/Extents.cs
Normal file
@@ -0,0 +1,157 @@
|
||||
// /***************************************************************************
|
||||
// Aaru Data Preservation Suite
|
||||
// ----------------------------------------------------------------------------
|
||||
//
|
||||
// Filename : Extents.cs
|
||||
// Author(s) : Rebecca Wallander <sakcheen@gmail.com>
|
||||
//
|
||||
// --[ Description ] ----------------------------------------------------------
|
||||
//
|
||||
// Gets physical sector extents for a file path.
|
||||
//
|
||||
// --[ License ] --------------------------------------------------------------
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as
|
||||
// published by the Free Software Foundation; either version 2.1 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful, but
|
||||
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
// ----------------------------------------------------------------------------
|
||||
// Copyright © 2026 Rebecca Wallander
|
||||
// ****************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Aaru.CommonTypes.Enums;
|
||||
using Aaru.Helpers;
|
||||
using Marshal = Aaru.Helpers.Marshal;
|
||||
|
||||
namespace Aaru.Filesystems;
|
||||
|
||||
public sealed partial class UDF
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets physical sector extents for a file path.
|
||||
/// </summary>
|
||||
/// <param name="path">Absolute filesystem path.</param>
|
||||
/// <param name="extents">Physical extents as (startSector, sectorCount).</param>
|
||||
/// <returns>Error number.</returns>
|
||||
public ErrorNumber GetFilePhysicalSectorExtents(string path, out List<(ulong startSector, uint sectorCount)> extents)
|
||||
{
|
||||
extents = [];
|
||||
|
||||
if(!_mounted) return ErrorNumber.AccessDenied;
|
||||
|
||||
ErrorNumber errno = GetFileEntryBuffer(path, out byte[] feBuffer, out ushort partitionReferenceNumber);
|
||||
|
||||
if(errno != ErrorNumber.NoError) return errno;
|
||||
|
||||
errno = ParseFileEntryInfo(feBuffer, out UdfFileEntryInfo info);
|
||||
|
||||
if(errno != ErrorNumber.NoError) return errno;
|
||||
|
||||
var adType = (byte)((ushort)info.IcbTag.flags & 0x07);
|
||||
|
||||
int fixedSize = info.IsExtended ? 216 : 176;
|
||||
int adOffset = fixedSize + (int)info.LengthOfExtendedAttributes;
|
||||
int adLength = (int)info.LengthOfAllocationDescriptors;
|
||||
|
||||
return adType switch
|
||||
{
|
||||
0 => CollectShortAdExtents(feBuffer,
|
||||
adOffset,
|
||||
adLength,
|
||||
partitionReferenceNumber,
|
||||
extents),
|
||||
1 => CollectLongAdExtents(feBuffer, adOffset, adLength, extents),
|
||||
2 => ErrorNumber.NotSupported,
|
||||
3 => ErrorNumber.NoError,
|
||||
_ => ErrorNumber.InvalidArgument
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Collects short allocation descriptor extents.
|
||||
/// </summary>
|
||||
/// <param name="feBuffer">File entry buffer.</param>
|
||||
/// <param name="adOffset">Allocation descriptor offset.</param>
|
||||
/// <param name="adLength">Allocation descriptor length.</param>
|
||||
/// <param name="partitionReferenceNumber">Partition reference number.</param>
|
||||
/// <param name="extents">Physical extents as (startSector, sectorCount).</param>
|
||||
/// <returns>Error number.</returns>
|
||||
ErrorNumber CollectShortAdExtents(byte[] feBuffer, int adOffset, int adLength, ushort partitionReferenceNumber,
|
||||
List<(ulong startSector, uint sectorCount)> extents)
|
||||
{
|
||||
int sadSize = System.Runtime.InteropServices.Marshal.SizeOf<ShortAllocationDescriptor>();
|
||||
int adPos = adOffset;
|
||||
|
||||
while(adPos + sadSize <= adOffset + adLength)
|
||||
{
|
||||
ShortAllocationDescriptor sad =
|
||||
Marshal.ByteArrayToStructureLittleEndian<ShortAllocationDescriptor>(feBuffer, adPos, sadSize);
|
||||
|
||||
uint extentLength = sad.extentLength & 0x3FFFFFFF;
|
||||
|
||||
if(extentLength == 0) break;
|
||||
|
||||
uint sectorCount = (extentLength + _sectorSize - 1) / _sectorSize;
|
||||
|
||||
if(sectorCount > 0)
|
||||
{
|
||||
ulong start = TranslateLogicalBlock(sad.extentLocation, partitionReferenceNumber, _partitionStartingLocation);
|
||||
extents.Add((start, sectorCount));
|
||||
}
|
||||
|
||||
adPos += sadSize;
|
||||
}
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Collects long allocation descriptor extents.
|
||||
/// </summary>
|
||||
/// <param name="feBuffer">File entry buffer.</param>
|
||||
/// <param name="adOffset">Allocation descriptor offset.</param>
|
||||
/// <param name="adLength">Allocation descriptor length.</param>
|
||||
/// <param name="extents">Physical extents as (startSector, sectorCount).</param>
|
||||
/// <returns>Error number.</returns>
|
||||
ErrorNumber CollectLongAdExtents(byte[] feBuffer, int adOffset, int adLength,
|
||||
List<(ulong startSector, uint sectorCount)> extents)
|
||||
{
|
||||
int ladSize = System.Runtime.InteropServices.Marshal.SizeOf<LongAllocationDescriptor>();
|
||||
int adPos = adOffset;
|
||||
|
||||
while(adPos + ladSize <= adOffset + adLength)
|
||||
{
|
||||
LongAllocationDescriptor lad =
|
||||
Marshal.ByteArrayToStructureLittleEndian<LongAllocationDescriptor>(feBuffer, adPos, ladSize);
|
||||
|
||||
uint extentLength = lad.extentLength & 0x3FFFFFFF;
|
||||
|
||||
if(extentLength == 0) break;
|
||||
|
||||
uint sectorCount = (extentLength + _sectorSize - 1) / _sectorSize;
|
||||
|
||||
if(sectorCount > 0)
|
||||
{
|
||||
ulong start = TranslateLogicalBlock(lad.extentLocation.logicalBlockNumber,
|
||||
lad.extentLocation.partitionReferenceNumber,
|
||||
_partitionStartingLocation);
|
||||
extents.Add((start, sectorCount));
|
||||
}
|
||||
|
||||
adPos += ladSize;
|
||||
}
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
}
|
||||
@@ -48,6 +48,8 @@ public sealed partial class ZZZRawImage
|
||||
readonly byte[] _cdSync = [0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00];
|
||||
readonly (MediaTagType tag, string name)[] _readWriteSidecars =
|
||||
[
|
||||
(MediaTagType.AacsMediaKey, ".mk.bin"), (MediaTagType.AacsVolumeUniqueKey, ".vuk.bin"),
|
||||
(MediaTagType.AACS_VolumeIdentifier, ".vid.bin"),
|
||||
(MediaTagType.ATA_IDENTIFY, ".identify.bin"), (MediaTagType.BD_DI, ".di.bin"),
|
||||
(MediaTagType.CD_ATIP, ".atip.bin"), (MediaTagType.CD_FullTOC, ".toc.bin"),
|
||||
(MediaTagType.CD_FirstTrackPregap, ".leadin.bin"), (MediaTagType.CD_PMA, ".pma.bin"),
|
||||
|
||||
189
Aaru.Images/ZZZRawImage/HdDvdIsoProbe.cs
Normal file
189
Aaru.Images/ZZZRawImage/HdDvdIsoProbe.cs
Normal file
@@ -0,0 +1,189 @@
|
||||
// /***************************************************************************
|
||||
// Aaru Data Preservation Suite
|
||||
// ----------------------------------------------------------------------------
|
||||
//
|
||||
// Filename : HdDvdIsoProbe.cs
|
||||
// Author(s) : Rebecca Wallander <sakcheen@gmail.com>
|
||||
//
|
||||
// Component : Disk image plugins.
|
||||
//
|
||||
// --[ Description ] ----------------------------------------------------------
|
||||
//
|
||||
// Detects HD DVD-Video layout on bare .iso images (HVDVD_TS folder).
|
||||
// Aaru.Core.Partitions.GetAll cannot be called from this assembly because
|
||||
// Aaru.Core references Aaru.Images; the partition walk below mirrors the
|
||||
// non-tape, in Partitions.GetAll. If we find a simpler way to detect a
|
||||
// HD DVD-Video layout, we should use that instead.
|
||||
//
|
||||
// --[ License ] --------------------------------------------------------------
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as
|
||||
// published by the Free Software Foundation; either version 2.1 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful, but
|
||||
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, see
|
||||
// <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
// ----------------------------------------------------------------------------
|
||||
// Copyright © 2026 Rebecca Wallander
|
||||
// ****************************************************************************/
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Aaru.CommonTypes;
|
||||
using Aaru.CommonTypes.Interfaces;
|
||||
using Aaru.CommonTypes.Structs;
|
||||
using Aaru.Decryption.Aacs;
|
||||
|
||||
namespace Aaru.Images;
|
||||
|
||||
public sealed partial class ZZZRawImage
|
||||
{
|
||||
/// <summary>True if a filesystem under some partition exposes the HD DVD-Video <c>HVDVD_TS</c> folder.</summary>
|
||||
bool TryDetectHdDvdVideoIso()
|
||||
{
|
||||
IMediaImage image = this;
|
||||
IOpticalMediaImage optical = this;
|
||||
PluginRegister plugins = PluginRegister.Singleton;
|
||||
List<Partition> parts = EnumeratePartitionsForHdDvdProbe(image);
|
||||
|
||||
foreach(Partition partition in parts)
|
||||
{
|
||||
foreach(IFilesystem fs in plugins.Filesystems.Values)
|
||||
{
|
||||
if(fs is not IReadOnlyFilesystem rofs)
|
||||
continue;
|
||||
|
||||
if(!fs.Identify(image, partition))
|
||||
continue;
|
||||
|
||||
if(HDDVD.HasHdDvdVideoTsFolder(optical, rofs, partition))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Lists partitions the same way as <see cref="Aaru.Core.Partitions.GetAll"/> for images that are not
|
||||
/// tape.
|
||||
/// </summary>
|
||||
static List<Partition> EnumeratePartitionsForHdDvdProbe(IMediaImage image)
|
||||
{
|
||||
PluginRegister plugins = PluginRegister.Singleton;
|
||||
List<Partition> foundPartitions = [];
|
||||
List<Partition> childPartitions = [];
|
||||
List<ulong> checkedLocations = [];
|
||||
|
||||
var partitionableImage = image as IPartitionableMediaImage;
|
||||
|
||||
// Getting all partitions from device (e.g. tracks)
|
||||
if(partitionableImage?.Partitions != null)
|
||||
{
|
||||
foreach(Partition imagePartition in partitionableImage.Partitions)
|
||||
{
|
||||
foreach(IPartition plugin in plugins.Partitions.Values)
|
||||
{
|
||||
if(plugin is null) continue;
|
||||
|
||||
if(!plugin.GetInformation(image, out List<Partition> partitions, imagePartition.Start)) continue;
|
||||
|
||||
foundPartitions.AddRange(partitions);
|
||||
}
|
||||
|
||||
checkedLocations.Add(imagePartition.Start);
|
||||
}
|
||||
}
|
||||
|
||||
if(!checkedLocations.Contains(0) && image.Info.Sectors > 0)
|
||||
{
|
||||
foreach(IPartition plugin in plugins.Partitions.Values)
|
||||
{
|
||||
if(plugin is null)
|
||||
continue;
|
||||
|
||||
if(!plugin.GetInformation(image, out List<Partition> partitions, 0))
|
||||
continue;
|
||||
|
||||
foundPartitions.AddRange(partitions);
|
||||
}
|
||||
|
||||
checkedLocations.Add(0);
|
||||
}
|
||||
|
||||
while(foundPartitions.Count > 0)
|
||||
{
|
||||
if(checkedLocations.Contains(foundPartitions[0].Start))
|
||||
{
|
||||
childPartitions.Add(foundPartitions[0]);
|
||||
foundPartitions.RemoveAt(0);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
List<Partition> children = [];
|
||||
|
||||
foreach(IPartition plugin in plugins.Partitions.Values)
|
||||
{
|
||||
if(plugin is null)
|
||||
continue;
|
||||
|
||||
if(!plugin.GetInformation(image, out List<Partition> partitions, foundPartitions[0].Start))
|
||||
continue;
|
||||
|
||||
children.AddRange(partitions);
|
||||
}
|
||||
|
||||
checkedLocations.Add(foundPartitions[0].Start);
|
||||
|
||||
if(children.Count > 0)
|
||||
{
|
||||
foundPartitions.RemoveAt(0);
|
||||
|
||||
foreach(Partition child in children)
|
||||
{
|
||||
if(checkedLocations.Contains(child.Start))
|
||||
childPartitions.Add(child);
|
||||
else
|
||||
foundPartitions.Add(child);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
childPartitions.Add(foundPartitions[0]);
|
||||
foundPartitions.RemoveAt(0);
|
||||
}
|
||||
}
|
||||
|
||||
if(partitionableImage is not null)
|
||||
{
|
||||
List<ulong> startLocations =
|
||||
childPartitions.ConvertAll(static detectedPartition => detectedPartition.Start);
|
||||
|
||||
if(partitionableImage.Partitions != null)
|
||||
{
|
||||
childPartitions.AddRange(partitionableImage.Partitions.Where(imagePartition =>
|
||||
!startLocations.Contains(imagePartition
|
||||
.Start)));
|
||||
}
|
||||
}
|
||||
|
||||
Partition[] childArray = childPartitions.OrderBy(static part => part.Start)
|
||||
.ThenBy(static part => part.Length)
|
||||
.ThenBy(static part => part.Scheme)
|
||||
.ToArray();
|
||||
|
||||
for(long i = 0; i < childArray.LongLength; i++)
|
||||
childArray[i].Sequence = (ulong)i;
|
||||
|
||||
return childArray.ToList();
|
||||
}
|
||||
}
|
||||
@@ -619,6 +619,16 @@ public sealed partial class ZZZRawImage
|
||||
else if(gcMagic == NGC_GC_MAGIC) _imageInfo.MediaType = MediaType.GOD;
|
||||
}
|
||||
|
||||
// Check for HD DVD video discs: .iso extension, size divisible by 2048, contains HVDVD_TS folder
|
||||
// Needed for identifying the correct AACS path, so we don't misidentify it as a BD video disc
|
||||
if(_extension == ".iso" &&
|
||||
_imageInfo.SectorSize == 2048 &&
|
||||
_imageInfo.ImageSize % 2048 == 0 &&
|
||||
_imageInfo.Sectors > 0 &&
|
||||
(_imageInfo.MediaType == MediaType.BDR || _imageInfo.MediaType == MediaType.BDRXL) &&
|
||||
TryDetectHdDvdVideoIso())
|
||||
_imageInfo.MediaType = MediaType.HDDVDROM;
|
||||
|
||||
// Check for bare .bca sidecar (64 bytes) for GameCube/Wii discs
|
||||
if(_imageInfo.MediaType is MediaType.GOD or MediaType.WOD && !_mediaTags.ContainsKey(MediaTagType.DVD_BCA))
|
||||
{
|
||||
|
||||
78
Aaru.Localization/Core.Designer.cs
generated
78
Aaru.Localization/Core.Designer.cs
generated
@@ -6958,5 +6958,83 @@ namespace Aaru.Localization {
|
||||
return ResourceManager.GetString("Output_format_not_initialized", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string Aacs_missing_volume_unique_key {
|
||||
get {
|
||||
return ResourceManager.GetString("Aacs_missing_volume_unique_key", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string Aacs_missing_unit_keys {
|
||||
get {
|
||||
return ResourceManager.GetString("Aacs_missing_unit_keys", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string Aacs_unit_key_inf_invalid {
|
||||
get {
|
||||
return ResourceManager.GetString("Aacs_unit_key_inf_invalid", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string Aacs_encrypted_unit_key_invalid_length {
|
||||
get {
|
||||
return ResourceManager.GetString("Aacs_encrypted_unit_key_invalid_length", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string Aacs2_unit_keys_not_supported {
|
||||
get {
|
||||
return ResourceManager.GetString("Aacs2_unit_keys_not_supported", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string Aacs_hddvd_not_supported {
|
||||
get {
|
||||
return ResourceManager.GetString("Aacs_hddvd_not_supported", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string Aacs_incomplete_cps_unit_at_track_end {
|
||||
get {
|
||||
return ResourceManager.GetString("Aacs_incomplete_cps_unit_at_track_end", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string Aacs_incomplete_cps_unit_at_track_end_continuing {
|
||||
get {
|
||||
return ResourceManager.GetString("Aacs_incomplete_cps_unit_at_track_end_continuing", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string Aacs_incomplete_cps_unit_pending_before_read_error {
|
||||
get {
|
||||
return ResourceManager.GetString("Aacs_incomplete_cps_unit_pending_before_read_error", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string Aacs_incomplete_cps_unit_after_read_error_continuing {
|
||||
get {
|
||||
return ResourceManager.GetString("Aacs_incomplete_cps_unit_after_read_error_continuing", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string Aacs_could_not_decrypt_cps_unit_starting_at_0 {
|
||||
get {
|
||||
return ResourceManager.GetString("Aacs_could_not_decrypt_cps_unit_starting_at_0", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string Aacs_could_not_decrypt_cps_unit_starting_at_0_continuing {
|
||||
get {
|
||||
return ResourceManager.GetString("Aacs_could_not_decrypt_cps_unit_starting_at_0_continuing", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string Aacs_bd_decrypt_requires_0_byte_sectors {
|
||||
get {
|
||||
return ResourceManager.GetString("Aacs_bd_decrypt_requires_0_byte_sectors", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3562,4 +3562,43 @@ It has no sense to do it, and it will put too much strain on the tape.</value>
|
||||
<data name="Output_format_not_initialized" xml:space="preserve">
|
||||
<value>[red]Output format not initialized.[/]</value>
|
||||
</data>
|
||||
<data name="Aacs_missing_volume_unique_key" xml:space="preserve">
|
||||
<value>Blu-ray AACS decryption requires AACS_VolumeUniqueKey media tag, or both AACS_MediaKey and AACS_VolumeIdentifier.</value>
|
||||
</data>
|
||||
<data name="Aacs_missing_unit_keys" xml:space="preserve">
|
||||
<value>Blu-ray AACS decryption requires CPS unit keys: AACS/Unit_Key_RO.inf on a mounted UDF/ISO partition, or AACS_DataKeys media tag.</value>
|
||||
</data>
|
||||
<data name="Aacs_unit_key_inf_invalid" xml:space="preserve">
|
||||
<value>AACS/Unit_Key_RO.inf could not be parsed.</value>
|
||||
</data>
|
||||
<data name="Aacs_encrypted_unit_key_invalid_length" xml:space="preserve">
|
||||
<value>Invalid encrypted CPS unit key length (expected 16 bytes).</value>
|
||||
</data>
|
||||
<data name="Aacs2_unit_keys_not_supported" xml:space="preserve">
|
||||
<value>AACS2 (Ultra HD Blu-ray) unit keys are not supported in this version.</value>
|
||||
</data>
|
||||
<data name="Aacs_hddvd_not_supported" xml:space="preserve">
|
||||
<value>HD DVD AACS decryption is not implemented.</value>
|
||||
</data>
|
||||
<data name="Aacs_incomplete_cps_unit_at_track_end" xml:space="preserve">
|
||||
<value>Incomplete AACS CPS unit at end of track ({0} sector(s) pending, starting at LBA {1}).</value>
|
||||
</data>
|
||||
<data name="Aacs_incomplete_cps_unit_at_track_end_continuing" xml:space="preserve">
|
||||
<value>Incomplete AACS CPS unit ({0} sector(s) pending, starting at LBA {1}); writing pending sectors as-is and continuing.</value>
|
||||
</data>
|
||||
<data name="Aacs_incomplete_cps_unit_pending_before_read_error" xml:space="preserve">
|
||||
<value>Cannot read sectors while an incomplete AACS CPS unit is pending (started at LBA {0}).</value>
|
||||
</data>
|
||||
<data name="Aacs_incomplete_cps_unit_after_read_error_continuing" xml:space="preserve">
|
||||
<value>Read failed with an incomplete AACS CPS unit pending ({0} sector(s) from LBA {1}); writing pending sectors as-is and continuing.</value>
|
||||
</data>
|
||||
<data name="Aacs_could_not_decrypt_cps_unit_starting_at_0" xml:space="preserve">
|
||||
<value>Could not decrypt AACS CPS unit starting at LBA {0}.</value>
|
||||
</data>
|
||||
<data name="Aacs_could_not_decrypt_cps_unit_starting_at_0_continuing" xml:space="preserve">
|
||||
<value>Could not decrypt AACS CPS unit starting at LBA {0}; writing ciphertext and continuing.</value>
|
||||
</data>
|
||||
<data name="Aacs_bd_decrypt_requires_0_byte_sectors" xml:space="preserve">
|
||||
<value>Blu-ray AACS decryption requires {0}-byte user sectors; this image reports a different sector size.</value>
|
||||
</data>
|
||||
</root>
|
||||
Reference in New Issue
Block a user