mirror of
https://github.com/aaru-dps/Aaru.git
synced 2026-07-08 18:16:24 +00:00
[convert] Add AACS pipeline
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)
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user