mirror of
https://github.com/aaru-dps/Aaru.git
synced 2025-12-16 11:14:25 +00:00
Rework image conversion command to separate into parts and move to Aaru.Core.
This commit is contained in:
72
Aaru.Core/Image/Convert/Capabilities.cs
Normal file
72
Aaru.Core/Image/Convert/Capabilities.cs
Normal file
@@ -0,0 +1,72 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Aaru.CommonTypes.Enums;
|
||||
using Aaru.Localization;
|
||||
|
||||
namespace Aaru.Core.Image;
|
||||
|
||||
public partial class Convert
|
||||
{
|
||||
/// <summary>
|
||||
/// Validates media type and media tag support in output format
|
||||
/// Checks if output format supports the media type being converted
|
||||
/// Validates all readable media tags are supported by output (unless force mode enabled)
|
||||
/// </summary>
|
||||
/// <returns>Error if required features not supported and data would be lost</returns>
|
||||
ErrorNumber ValidateMediaCapabilities()
|
||||
{
|
||||
if(!_outputImage.SupportedMediaTypes.Contains(_mediaType))
|
||||
{
|
||||
StoppingErrorMessage?.Invoke(UI.Output_format_does_not_support_media_type);
|
||||
|
||||
return ErrorNumber.UnsupportedMedia;
|
||||
}
|
||||
|
||||
foreach(MediaTagType mediaTag in _inputImage.Info.ReadableMediaTags.Where(mediaTag =>
|
||||
!_outputImage.SupportedMediaTags.Contains(mediaTag) && !_force))
|
||||
{
|
||||
StoppingErrorMessage?.Invoke(string.Format(UI.Converting_image_will_lose_media_tag_0 +
|
||||
Environment.NewLine +
|
||||
UI.If_you_dont_care_use_force_option,
|
||||
mediaTag));
|
||||
|
||||
return ErrorNumber.DataWillBeLost;
|
||||
}
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
|
||||
ErrorNumber ValidateSectorTags(out bool useLong)
|
||||
{
|
||||
// Validates sector tag compatibility between formats
|
||||
// Sets useLong flag based on sector tag support to determine sector size (512 vs 2352 bytes)
|
||||
// Some tags like CD flags/ISRC don't require long sectors; subchannel data does
|
||||
// In force mode, skips unsupported tags; otherwise reports error if data would be lost
|
||||
|
||||
useLong = _inputImage.Info.ReadableSectorTags.Count != 0;
|
||||
|
||||
foreach(SectorTagType sectorTag in _inputImage.Info.ReadableSectorTags.Where(sectorTag =>
|
||||
!_outputImage.SupportedSectorTags.Contains(sectorTag)))
|
||||
{
|
||||
if(_force)
|
||||
{
|
||||
if(sectorTag != SectorTagType.CdTrackFlags &&
|
||||
sectorTag != SectorTagType.CdTrackIsrc &&
|
||||
sectorTag != SectorTagType.CdSectorSubchannel)
|
||||
useLong = false;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
StoppingErrorMessage.Invoke(string.Format(UI.Converting_image_will_lose_sector_tag_0 +
|
||||
Environment.NewLine +
|
||||
UI
|
||||
.If_you_dont_care_use_force_option_This_will_skip_all_sector_tags_converting_only_user_data,
|
||||
sectorTag));
|
||||
|
||||
return ErrorNumber.DataWillBeLost;
|
||||
}
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
}
|
||||
358
Aaru.Core/Image/Convert/Convert.cs
Normal file
358
Aaru.Core/Image/Convert/Convert.cs
Normal file
@@ -0,0 +1,358 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Aaru.CommonTypes;
|
||||
using Aaru.CommonTypes.AaruMetadata;
|
||||
using Aaru.CommonTypes.Enums;
|
||||
using Aaru.CommonTypes.Interfaces;
|
||||
using Aaru.CommonTypes.Metadata;
|
||||
using Aaru.Localization;
|
||||
using MediaType = Aaru.CommonTypes.MediaType;
|
||||
using TapeFile = Aaru.CommonTypes.Structs.TapeFile;
|
||||
using TapePartition = Aaru.CommonTypes.Structs.TapePartition;
|
||||
|
||||
namespace Aaru.Core.Image;
|
||||
|
||||
public partial class Convert
|
||||
{
|
||||
const string MODULE_NAME = "Image Conversion";
|
||||
readonly string _comments;
|
||||
readonly uint _count;
|
||||
readonly string _creator;
|
||||
readonly bool _decrypt;
|
||||
readonly string _driveFirmwareRevision;
|
||||
readonly string _driveManufacturer;
|
||||
readonly string _driveModel;
|
||||
readonly string _driveSerialNumber;
|
||||
readonly bool _fixSubchannel;
|
||||
readonly bool _fixSubchannelCrc;
|
||||
readonly bool _fixSubchannelPosition;
|
||||
readonly bool _force;
|
||||
readonly bool _generateSubchannels;
|
||||
readonly (uint cylinders, uint heads, uint sectors)? _geometryValues;
|
||||
readonly IMediaImage _inputImage;
|
||||
readonly int _lastMediaSequence;
|
||||
readonly string _mediaBarcode;
|
||||
readonly string _mediaManufacturer;
|
||||
readonly string _mediaModel;
|
||||
readonly string _mediaPartNumber;
|
||||
readonly int _mediaSequence;
|
||||
readonly string _mediaSerialNumber;
|
||||
readonly string _mediaTitle;
|
||||
readonly MediaType _mediaType;
|
||||
readonly uint _negativeSectors;
|
||||
readonly IWritableImage _outputImage;
|
||||
readonly string _outputPath;
|
||||
readonly uint _overflowSectors;
|
||||
readonly Dictionary<string, string> _parsedOptions;
|
||||
readonly PluginRegister _plugins;
|
||||
readonly Resume _resume;
|
||||
readonly Metadata _sidecar;
|
||||
bool _aborted;
|
||||
|
||||
// TODO: Abort
|
||||
public Convert(IMediaImage inputImage, IWritableImage outputImage, MediaType mediaType, bool force,
|
||||
string outputPath, Dictionary<string, string> parsedOptions, uint negativeSectors,
|
||||
uint overflowSectors, string comments, string creator, string driveFirmwareRevision,
|
||||
string driveManufacturer, string driveModel, string driveSerialNumber, int lastMediaSequence,
|
||||
string mediaBarcode, string mediaManufacturer, string mediaModel, string mediaPartNumber,
|
||||
int mediaSequence, string mediaSerialNumber, string mediaTitle, bool decrypt, uint count,
|
||||
PluginRegister plugins, bool fixSubchannelPosition, bool fixSubchannel, bool fixSubchannelCrc,
|
||||
bool generateSubchannels, (uint cylinders, uint heads, uint sectors)? geometryValues, Resume resume,
|
||||
Metadata sidecar)
|
||||
{
|
||||
_inputImage = inputImage;
|
||||
_outputImage = outputImage;
|
||||
_mediaType = mediaType;
|
||||
_force = force;
|
||||
_outputPath = outputPath;
|
||||
_parsedOptions = parsedOptions;
|
||||
_negativeSectors = negativeSectors;
|
||||
_overflowSectors = overflowSectors;
|
||||
_comments = comments;
|
||||
_creator = creator;
|
||||
_driveFirmwareRevision = driveFirmwareRevision;
|
||||
_driveManufacturer = driveManufacturer;
|
||||
_driveModel = driveModel;
|
||||
_driveSerialNumber = driveSerialNumber;
|
||||
_lastMediaSequence = lastMediaSequence;
|
||||
_mediaBarcode = mediaBarcode;
|
||||
_mediaManufacturer = mediaManufacturer;
|
||||
_mediaModel = mediaModel;
|
||||
_mediaPartNumber = mediaPartNumber;
|
||||
_mediaSequence = mediaSequence;
|
||||
_mediaSerialNumber = mediaSerialNumber;
|
||||
_mediaTitle = mediaTitle;
|
||||
_decrypt = decrypt;
|
||||
_count = count;
|
||||
_plugins = plugins;
|
||||
_fixSubchannelPosition = fixSubchannelPosition;
|
||||
_fixSubchannel = fixSubchannel;
|
||||
_fixSubchannelCrc = fixSubchannelCrc;
|
||||
_generateSubchannels = generateSubchannels;
|
||||
_geometryValues = geometryValues;
|
||||
_resume = resume;
|
||||
_sidecar = sidecar;
|
||||
}
|
||||
|
||||
public ErrorNumber Start()
|
||||
{
|
||||
// Validate that output format supports the media type and tags
|
||||
ErrorNumber errno = ValidateMediaCapabilities();
|
||||
|
||||
if(errno != ErrorNumber.NoError) return errno;
|
||||
|
||||
// Validate sector tags compatibility between formats
|
||||
errno = ValidateSectorTags(out bool useLong);
|
||||
|
||||
if(errno != ErrorNumber.NoError) return errno;
|
||||
|
||||
// Check and setup tape image support if needed
|
||||
var inputTape = _inputImage as ITapeImage;
|
||||
var outputTape = _outputImage as IWritableTapeImage;
|
||||
|
||||
errno = ValidateTapeImage(inputTape, outputTape);
|
||||
|
||||
if(errno != ErrorNumber.NoError) return errno;
|
||||
|
||||
var ret = false;
|
||||
|
||||
errno = SetupTapeImage(inputTape, outputTape);
|
||||
|
||||
if(errno != ErrorNumber.NoError) return errno;
|
||||
|
||||
// Validate optical media capabilities (sessions, hidden tracks, etc.)
|
||||
if((_outputImage as IWritableOpticalImage)?.OpticalCapabilities.HasFlag(OpticalImageCapabilities
|
||||
.CanStoreSessions) !=
|
||||
true &&
|
||||
(_inputImage as IOpticalMediaImage)?.Sessions?.Count > 1)
|
||||
{
|
||||
// TODO: Disabled until 6.0
|
||||
/*if(!_force)
|
||||
{*/
|
||||
StoppingErrorMessage?.Invoke(Localization.Core.Output_format_does_not_support_sessions);
|
||||
|
||||
return ErrorNumber.UnsupportedMedia;
|
||||
/*}
|
||||
|
||||
StoppingErrorMessage?.Invoke("Output format does not support sessions, this will end in a loss of data, continuing...");*/
|
||||
}
|
||||
|
||||
// Check for hidden tracks support in optical media
|
||||
if((_outputImage as IWritableOpticalImage)?.OpticalCapabilities.HasFlag(OpticalImageCapabilities
|
||||
.CanStoreHiddenTracks) !=
|
||||
true &&
|
||||
(_inputImage as IOpticalMediaImage)?.Tracks?.Any(static t => t.Sequence == 0) == true)
|
||||
{
|
||||
// TODO: Disabled until 6.0
|
||||
/*if(!_force)
|
||||
{*/
|
||||
StoppingErrorMessage?.Invoke(Localization.Core.Output_format_does_not_support_hidden_tracks);
|
||||
|
||||
return ErrorNumber.UnsupportedMedia;
|
||||
/*}
|
||||
|
||||
StoppingErrorMessage?.Invoke("Output format does not support sessions, this will end in a loss of data, continuing...");*/
|
||||
}
|
||||
|
||||
// Create the output image file with appropriate settings
|
||||
errno = CreateOutputImage();
|
||||
|
||||
if(errno != ErrorNumber.NoError) return errno;
|
||||
|
||||
// Set image metadata in the output file
|
||||
errno = SetImageMetadata();
|
||||
|
||||
if(errno != ErrorNumber.NoError) return errno;
|
||||
|
||||
// Prepare metadata and dump hardware information
|
||||
Metadata metadata = _inputImage.AaruMetadata;
|
||||
List<DumpHardware> dumpHardware = _inputImage.DumpHardware;
|
||||
|
||||
// Convert media tags from input to output format
|
||||
errno = ConvertMediaTags();
|
||||
|
||||
if(errno != ErrorNumber.NoError && !_force) return errno;
|
||||
|
||||
UpdateStatus?.Invoke(string.Format(UI._0_sectors_to_convert, _inputImage.Info.Sectors));
|
||||
|
||||
// Perform the actual data conversion from input to output image
|
||||
if(_inputImage is IOpticalMediaImage inputOptical &&
|
||||
_outputImage is IWritableOpticalImage outputOptical &&
|
||||
inputOptical.Tracks != null)
|
||||
{
|
||||
errno = ConvertOptical(inputOptical, outputOptical, useLong);
|
||||
|
||||
if(errno != ErrorNumber.NoError) return errno;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(inputTape == null || outputTape == null || !inputTape.IsTape)
|
||||
{
|
||||
(uint cylinders, uint heads, uint sectors) chs =
|
||||
_geometryValues != null
|
||||
? (_geometryValues.Value.cylinders, _geometryValues.Value.heads, _geometryValues.Value.sectors)
|
||||
: (_inputImage.Info.Cylinders, _inputImage.Info.Heads, _inputImage.Info.SectorsPerTrack);
|
||||
|
||||
UpdateStatus?.Invoke(string.Format(UI.Setting_geometry_to_0_cylinders_1_heads_and_2_sectors_per_track,
|
||||
chs.cylinders,
|
||||
chs.heads,
|
||||
chs.sectors));
|
||||
|
||||
if(!_outputImage.SetGeometry(chs.cylinders, chs.heads, chs.sectors))
|
||||
{
|
||||
ErrorMessage?.Invoke(string.Format(UI.Error_0_setting_geometry_image_may_be_incorrect_continuing,
|
||||
_outputImage.ErrorMessage));
|
||||
}
|
||||
}
|
||||
|
||||
errno = ConvertSectors(useLong, inputTape?.IsTape == true);
|
||||
|
||||
if(errno != ErrorNumber.NoError) return errno;
|
||||
|
||||
errno = ConvertSectorsTags(useLong);
|
||||
|
||||
if(errno != ErrorNumber.NoError) return errno;
|
||||
|
||||
if(_inputImage is IFluxImage inputFlux && _outputImage is IWritableFluxImage outputFlux)
|
||||
{
|
||||
errno = ConvertFlux(inputFlux, outputFlux);
|
||||
|
||||
if(errno != ErrorNumber.NoError) return errno;
|
||||
}
|
||||
|
||||
if(inputTape != null && outputTape != null && inputTape.IsTape)
|
||||
{
|
||||
InitProgress?.Invoke();
|
||||
var currentFile = 0;
|
||||
|
||||
foreach(TapeFile tapeFile in inputTape.Files)
|
||||
{
|
||||
UpdateProgress?.Invoke(string.Format(UI.Converting_file_0_of_partition_1,
|
||||
tapeFile.File,
|
||||
tapeFile.Partition),
|
||||
currentFile + 1,
|
||||
inputTape.Files.Count);
|
||||
|
||||
outputTape.AddFile(tapeFile);
|
||||
currentFile++;
|
||||
}
|
||||
|
||||
EndProgress?.Invoke();
|
||||
|
||||
InitProgress?.Invoke();
|
||||
var currentPartition = 0;
|
||||
|
||||
foreach(TapePartition tapePartition in inputTape.TapePartitions)
|
||||
{
|
||||
UpdateProgress?.Invoke(string.Format(UI.Converting_tape_partition_0, tapePartition.Number),
|
||||
currentPartition + 1,
|
||||
inputTape.TapePartitions.Count);
|
||||
|
||||
outputTape.AddPartition(tapePartition);
|
||||
currentPartition++;
|
||||
}
|
||||
|
||||
EndProgress?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
if(_negativeSectors > 0)
|
||||
{
|
||||
errno = ConvertNegativeSectors(useLong);
|
||||
|
||||
if(errno != ErrorNumber.NoError) return errno;
|
||||
}
|
||||
|
||||
if(_overflowSectors > 0)
|
||||
{
|
||||
errno = ConvertOverflowSectors(useLong);
|
||||
|
||||
if(errno != ErrorNumber.NoError) return errno;
|
||||
}
|
||||
|
||||
if(_resume != null || dumpHardware != null)
|
||||
{
|
||||
InitProgress?.Invoke();
|
||||
|
||||
PulseProgress?.Invoke(UI.Writing_dump_hardware_list);
|
||||
|
||||
if(_resume != null)
|
||||
ret = _outputImage.SetDumpHardware(_resume.Tries);
|
||||
else if(dumpHardware != null) ret = _outputImage.SetDumpHardware(dumpHardware);
|
||||
|
||||
if(ret) UpdateStatus?.Invoke(UI.Written_dump_hardware_list_to_output_image);
|
||||
|
||||
EndProgress?.Invoke();
|
||||
}
|
||||
|
||||
ret = false;
|
||||
|
||||
if(_sidecar != null || metadata != null)
|
||||
{
|
||||
InitProgress?.Invoke();
|
||||
PulseProgress?.Invoke(UI.Writing_metadata);
|
||||
|
||||
if(_sidecar != null)
|
||||
ret = _outputImage.SetMetadata(_sidecar);
|
||||
else if(metadata != null) ret = _outputImage.SetMetadata(metadata);
|
||||
|
||||
if(ret) UpdateStatus?.Invoke(UI.Written_Aaru_Metadata_to_output_image);
|
||||
|
||||
EndProgress?.Invoke();
|
||||
}
|
||||
|
||||
var closed = false;
|
||||
|
||||
InitProgress?.Invoke();
|
||||
PulseProgress?.Invoke(UI.Closing_output_image);
|
||||
closed = _outputImage.Close();
|
||||
EndProgress?.Invoke();
|
||||
|
||||
if(!closed)
|
||||
{
|
||||
StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_closing_output_image_Contents_are_not_correct,
|
||||
_outputImage.ErrorMessage));
|
||||
|
||||
return ErrorNumber.WriteError;
|
||||
}
|
||||
|
||||
UpdateStatus?.Invoke(UI.Conversion_done);
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
|
||||
/// <summary>Event raised when the progress bar is no longer needed</summary>
|
||||
public event EndProgressHandler EndProgress;
|
||||
|
||||
/// <summary>Event raised when a progress bar is needed</summary>
|
||||
public event InitProgressHandler InitProgress;
|
||||
|
||||
/// <summary>Event raised to report status updates</summary>
|
||||
public event UpdateStatusHandler UpdateStatus;
|
||||
|
||||
/// <summary>Event raised to report a non-fatal error</summary>
|
||||
public event ErrorMessageHandler ErrorMessage;
|
||||
|
||||
/// <summary>Event raised to report a fatal error that stops the dumping operation and should call user's attention</summary>
|
||||
public event ErrorMessageHandler StoppingErrorMessage;
|
||||
|
||||
/// <summary>Event raised to update the values of a determinate progress bar</summary>
|
||||
public event UpdateProgressHandler UpdateProgress;
|
||||
|
||||
/// <summary>Event raised to update the status of an indeterminate progress bar</summary>
|
||||
public event PulseProgressHandler PulseProgress;
|
||||
|
||||
/// <summary>Event raised when the progress bar is no longer needed</summary>
|
||||
public event EndProgressHandler2 EndProgress2;
|
||||
|
||||
/// <summary>Event raised when a progress bar is needed</summary>
|
||||
public event InitProgressHandler2 InitProgress2;
|
||||
|
||||
/// <summary>Event raised to update the values of a determinate progress bar</summary>
|
||||
public event UpdateProgressHandler2 UpdateProgress2;
|
||||
|
||||
public void Abort()
|
||||
{
|
||||
_aborted = true;
|
||||
}
|
||||
}
|
||||
35
Aaru.Core/Image/Convert/Create.cs
Normal file
35
Aaru.Core/Image/Convert/Create.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using Aaru.CommonTypes.Enums;
|
||||
using Aaru.Localization;
|
||||
|
||||
namespace Aaru.Core.Image;
|
||||
|
||||
public partial class Convert
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates output image file with specified parameters
|
||||
/// Calls the output format plugin's Create() method with sector count and format options
|
||||
/// Shows progress indicator during file creation
|
||||
/// </summary>
|
||||
/// <returns>Error code if creation fails</returns>
|
||||
private ErrorNumber CreateOutputImage()
|
||||
{
|
||||
InitProgress?.Invoke();
|
||||
PulseProgress?.Invoke(UI.Invoke_Opening_image_file);
|
||||
|
||||
bool created = _outputImage.Create(_outputPath,
|
||||
_mediaType,
|
||||
_parsedOptions,
|
||||
_inputImage.Info.Sectors,
|
||||
_negativeSectors,
|
||||
_overflowSectors,
|
||||
_inputImage.Info.SectorSize);
|
||||
|
||||
EndProgress?.Invoke();
|
||||
|
||||
if(created) return ErrorNumber.NoError;
|
||||
|
||||
StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_creating_output_image, _outputImage.ErrorMessage));
|
||||
|
||||
return ErrorNumber.CannotCreateFormat;
|
||||
}
|
||||
}
|
||||
339
Aaru.Core/Image/Convert/Edge.cs
Normal file
339
Aaru.Core/Image/Convert/Edge.cs
Normal file
@@ -0,0 +1,339 @@
|
||||
using System.Linq;
|
||||
using Aaru.CommonTypes.Enums;
|
||||
using Aaru.Localization;
|
||||
using Aaru.Logging;
|
||||
|
||||
namespace Aaru.Core.Image;
|
||||
|
||||
public partial class Convert
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts negative sectors (pre-gap) from input to output image
|
||||
/// Handles both long and short sector formats with progress indication
|
||||
/// Also converts associated sector tags if present
|
||||
/// </summary>
|
||||
/// <returns>Error code if conversion fails in non-force mode</returns>
|
||||
ErrorNumber ConvertNegativeSectors(bool useLong)
|
||||
{
|
||||
ErrorNumber errno = ErrorNumber.NoError;
|
||||
|
||||
InitProgress?.Invoke();
|
||||
|
||||
// There's no -0
|
||||
for(uint i = 1; i <= _negativeSectors; i++)
|
||||
{
|
||||
byte[] sector;
|
||||
|
||||
UpdateProgress?.Invoke(string.Format(UI.Converting_negative_sector_0_of_1, i, _negativeSectors),
|
||||
i,
|
||||
_negativeSectors);
|
||||
|
||||
bool result;
|
||||
SectorStatus sectorStatus;
|
||||
|
||||
if(useLong)
|
||||
{
|
||||
errno = _inputImage.ReadSectorLong(i, true, out sector, out sectorStatus);
|
||||
|
||||
if(errno == ErrorNumber.NoError)
|
||||
result = _outputImage.WriteSectorLong(sector, i, true, sectorStatus);
|
||||
else
|
||||
{
|
||||
result = true;
|
||||
|
||||
if(_force)
|
||||
ErrorMessage?.Invoke(string.Format(UI.Error_0_reading_negative_sector_1_continuing, errno, i));
|
||||
else
|
||||
{
|
||||
StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_reading_negative_sector_1_not_continuing,
|
||||
errno,
|
||||
i));
|
||||
|
||||
return errno;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
errno = _inputImage.ReadSector(i, true, out sector, out sectorStatus);
|
||||
|
||||
if(errno == ErrorNumber.NoError)
|
||||
result = _outputImage.WriteSector(sector, i, true, sectorStatus);
|
||||
else
|
||||
{
|
||||
result = true;
|
||||
|
||||
if(_force)
|
||||
ErrorMessage?.Invoke(string.Format(UI.Error_0_reading_negative_sector_1_continuing, errno, i));
|
||||
else
|
||||
{
|
||||
StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_reading_negative_sector_1_not_continuing,
|
||||
errno,
|
||||
i));
|
||||
|
||||
return errno;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(result) continue;
|
||||
|
||||
if(_force)
|
||||
{
|
||||
ErrorMessage?.Invoke(string.Format(UI.Error_0_writing_negative_sector_1_continuing,
|
||||
_outputImage.ErrorMessage,
|
||||
i));
|
||||
}
|
||||
else
|
||||
{
|
||||
StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_writing_negative_sector_1_not_continuing,
|
||||
_outputImage.ErrorMessage,
|
||||
i));
|
||||
|
||||
return ErrorNumber.WriteError;
|
||||
}
|
||||
}
|
||||
|
||||
EndProgress?.Invoke();
|
||||
|
||||
foreach(SectorTagType tag in _inputImage.Info.ReadableSectorTags.TakeWhile(_ => useLong))
|
||||
{
|
||||
switch(tag)
|
||||
{
|
||||
case SectorTagType.AppleSonyTag:
|
||||
case SectorTagType.AppleProfileTag:
|
||||
case SectorTagType.PriamDataTowerTag:
|
||||
case SectorTagType.CdSectorSync:
|
||||
case SectorTagType.CdSectorHeader:
|
||||
case SectorTagType.CdSectorSubHeader:
|
||||
case SectorTagType.CdSectorEdc:
|
||||
case SectorTagType.CdSectorEccP:
|
||||
case SectorTagType.CdSectorEccQ:
|
||||
case SectorTagType.CdSectorEcc:
|
||||
// These tags are inline in long sector
|
||||
continue;
|
||||
case SectorTagType.CdTrackFlags:
|
||||
case SectorTagType.CdTrackIsrc:
|
||||
case SectorTagType.CdTrackText:
|
||||
// These tags are track tags
|
||||
continue;
|
||||
}
|
||||
|
||||
if(_force && !_outputImage.SupportedSectorTags.Contains(tag)) continue;
|
||||
|
||||
InitProgress?.Invoke();
|
||||
|
||||
for(uint i = 1; i <= _negativeSectors; i++)
|
||||
{
|
||||
UpdateProgress?.Invoke(string.Format(UI.Converting_tag_1_for_negative_sector_0, i, tag),
|
||||
i,
|
||||
_negativeSectors);
|
||||
|
||||
bool result;
|
||||
|
||||
errno = _inputImage.ReadSectorTag(i, true, tag, out byte[] sector);
|
||||
|
||||
if(errno == ErrorNumber.NoError)
|
||||
result = _outputImage.WriteSectorTag(sector, i, true, tag);
|
||||
else
|
||||
{
|
||||
result = true;
|
||||
|
||||
if(_force)
|
||||
AaruLogging.Error(UI.Error_0_reading_negative_sector_1_continuing, errno, i);
|
||||
else
|
||||
{
|
||||
AaruLogging.Error(UI.Error_0_reading_negative_sector_1_not_continuing, errno, i);
|
||||
|
||||
return errno;
|
||||
}
|
||||
}
|
||||
|
||||
if(result) continue;
|
||||
|
||||
if(_force)
|
||||
AaruLogging.Error(UI.Error_0_writing_negative_sector_1_continuing, _outputImage.ErrorMessage, i);
|
||||
else
|
||||
{
|
||||
AaruLogging.Error(UI.Error_0_writing_negative_sector_1_not_continuing,
|
||||
_outputImage.ErrorMessage,
|
||||
i);
|
||||
|
||||
return ErrorNumber.WriteError;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return errno;
|
||||
}
|
||||
|
||||
ErrorNumber ConvertOverflowSectors(bool useLong)
|
||||
{
|
||||
// Converts overflow sectors (lead-out) from input to output image
|
||||
// Handles both long and short sector formats with progress indication
|
||||
// Also converts associated sector tags if present
|
||||
// Returns error code if conversion fails in non-force mode
|
||||
|
||||
ErrorNumber errno = ErrorNumber.NoError;
|
||||
|
||||
InitProgress?.Invoke();
|
||||
|
||||
for(uint i = 0; i < _overflowSectors; i++)
|
||||
{
|
||||
byte[] sector;
|
||||
|
||||
UpdateProgress?.Invoke(string.Format(UI.Converting_overflow_sector_0_of_1, i, _overflowSectors),
|
||||
i,
|
||||
_overflowSectors);
|
||||
|
||||
bool result;
|
||||
SectorStatus sectorStatus;
|
||||
|
||||
if(useLong)
|
||||
{
|
||||
errno = _inputImage.ReadSectorLong(_inputImage.Info.Sectors + i, false, out sector, out sectorStatus);
|
||||
|
||||
if(errno == ErrorNumber.NoError)
|
||||
result = _outputImage.WriteSectorLong(sector, _inputImage.Info.Sectors + i, false, sectorStatus);
|
||||
else
|
||||
{
|
||||
result = true;
|
||||
|
||||
if(_force)
|
||||
ErrorMessage?.Invoke(string.Format(UI.Error_0_reading_overflow_sector_1_continuing, errno, i));
|
||||
else
|
||||
{
|
||||
StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_reading_overflow_sector_1_not_continuing,
|
||||
errno,
|
||||
i));
|
||||
|
||||
return errno;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
errno = _inputImage.ReadSector(_inputImage.Info.Sectors + i, false, out sector, out sectorStatus);
|
||||
|
||||
if(errno == ErrorNumber.NoError)
|
||||
result = _outputImage.WriteSector(sector, _inputImage.Info.Sectors + i, false, sectorStatus);
|
||||
else
|
||||
{
|
||||
result = true;
|
||||
|
||||
if(_force)
|
||||
ErrorMessage?.Invoke(string.Format(UI.Error_0_reading_overflow_sector_1_continuing, errno, i));
|
||||
else
|
||||
{
|
||||
StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_reading_overflow_sector_1_not_continuing,
|
||||
errno,
|
||||
i));
|
||||
|
||||
return errno;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(result) continue;
|
||||
|
||||
if(_force)
|
||||
{
|
||||
ErrorMessage?.Invoke(string.Format(UI.Error_0_writing_overflow_sector_1_continuing,
|
||||
_outputImage.ErrorMessage,
|
||||
i));
|
||||
}
|
||||
else
|
||||
{
|
||||
StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_writing_overflow_sector_1_not_continuing,
|
||||
_outputImage.ErrorMessage,
|
||||
i));
|
||||
|
||||
return ErrorNumber.WriteError;
|
||||
}
|
||||
}
|
||||
|
||||
EndProgress?.Invoke();
|
||||
|
||||
foreach(SectorTagType tag in _inputImage.Info.ReadableSectorTags.TakeWhile(_ => useLong))
|
||||
{
|
||||
switch(tag)
|
||||
{
|
||||
case SectorTagType.AppleSonyTag:
|
||||
case SectorTagType.AppleProfileTag:
|
||||
case SectorTagType.PriamDataTowerTag:
|
||||
case SectorTagType.CdSectorSync:
|
||||
case SectorTagType.CdSectorHeader:
|
||||
case SectorTagType.CdSectorSubHeader:
|
||||
case SectorTagType.CdSectorEdc:
|
||||
case SectorTagType.CdSectorEccP:
|
||||
case SectorTagType.CdSectorEccQ:
|
||||
case SectorTagType.CdSectorEcc:
|
||||
// These tags are inline in long sector
|
||||
continue;
|
||||
case SectorTagType.CdTrackFlags:
|
||||
case SectorTagType.CdTrackIsrc:
|
||||
case SectorTagType.CdTrackText:
|
||||
// These tags are track tags
|
||||
continue;
|
||||
}
|
||||
|
||||
if(_force && !_outputImage.SupportedSectorTags.Contains(tag)) continue;
|
||||
|
||||
InitProgress?.Invoke();
|
||||
|
||||
for(uint i = 1; i <= _overflowSectors; i++)
|
||||
{
|
||||
UpdateProgress?.Invoke(string.Format(UI.Converting_tag_1_for_overflow_sector_0, i, tag),
|
||||
i,
|
||||
_overflowSectors);
|
||||
|
||||
bool result;
|
||||
|
||||
errno = _inputImage.ReadSectorTag(_inputImage.Info.Sectors + i, false, tag, out byte[] sector);
|
||||
|
||||
if(errno == ErrorNumber.NoError)
|
||||
result = _outputImage.WriteSectorTag(sector, _inputImage.Info.Sectors + i, false, tag);
|
||||
else
|
||||
{
|
||||
result = true;
|
||||
|
||||
if(_force)
|
||||
{
|
||||
ErrorMessage?.Invoke(string.Format(UI.Error_0_reading_overflow_sector_1_continuing,
|
||||
errno,
|
||||
_inputImage.Info.Sectors + i));
|
||||
}
|
||||
else
|
||||
{
|
||||
StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_reading_overflow_sector_1_not_continuing,
|
||||
errno,
|
||||
_inputImage.Info.Sectors + i));
|
||||
|
||||
return errno;
|
||||
}
|
||||
}
|
||||
|
||||
if(result) continue;
|
||||
|
||||
if(_force)
|
||||
{
|
||||
ErrorMessage?.Invoke(string.Format(UI.Error_0_writing_overflow_sector_1_continuing,
|
||||
_outputImage.ErrorMessage,
|
||||
_inputImage.Info.Sectors + i));
|
||||
}
|
||||
else
|
||||
{
|
||||
StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_writing_overflow_sector_1_not_continuing,
|
||||
_outputImage.ErrorMessage,
|
||||
_inputImage.Info.Sectors + i));
|
||||
|
||||
return ErrorNumber.WriteError;
|
||||
}
|
||||
}
|
||||
|
||||
EndProgress?.Invoke();
|
||||
}
|
||||
|
||||
return errno;
|
||||
}
|
||||
}
|
||||
52
Aaru.Core/Image/Convert/Flux.cs
Normal file
52
Aaru.Core/Image/Convert/Flux.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
using Aaru.CommonTypes.Enums;
|
||||
using Aaru.CommonTypes.Interfaces;
|
||||
|
||||
namespace Aaru.Core.Image;
|
||||
|
||||
public partial class Convert
|
||||
{
|
||||
// TODO: Should we return error any time?
|
||||
// TODO: Add progress reporting
|
||||
ErrorNumber ConvertFlux(IFluxImage inputFlux, IWritableFluxImage outputFlux)
|
||||
{
|
||||
for(ushort track = 0; track < inputFlux.Info.Cylinders; track++)
|
||||
{
|
||||
for(uint head = 0; head < inputFlux.Info.Heads; head++)
|
||||
{
|
||||
ErrorNumber error = inputFlux.SubTrackLength(head, track, out byte subTrackLen);
|
||||
|
||||
if(error != ErrorNumber.NoError) continue;
|
||||
|
||||
for(byte subTrackIndex = 0; subTrackIndex < subTrackLen; subTrackIndex++)
|
||||
{
|
||||
error = inputFlux.CapturesLength(head, track, subTrackIndex, out uint capturesLen);
|
||||
|
||||
if(error != ErrorNumber.NoError) continue;
|
||||
|
||||
for(uint captureIndex = 0; captureIndex < capturesLen; captureIndex++)
|
||||
{
|
||||
inputFlux.ReadFluxCapture(head,
|
||||
track,
|
||||
subTrackIndex,
|
||||
captureIndex,
|
||||
out ulong indexResolution,
|
||||
out ulong dataResolution,
|
||||
out byte[] indexBuffer,
|
||||
out byte[] dataBuffer);
|
||||
|
||||
outputFlux.WriteFluxCapture(indexResolution,
|
||||
dataResolution,
|
||||
indexBuffer,
|
||||
dataBuffer,
|
||||
head,
|
||||
track,
|
||||
subTrackIndex,
|
||||
captureIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
}
|
||||
49
Aaru.Core/Image/Convert/Metadata.cs
Normal file
49
Aaru.Core/Image/Convert/Metadata.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using Aaru.CommonTypes.Enums;
|
||||
using Aaru.CommonTypes.Interop;
|
||||
using Aaru.Localization;
|
||||
|
||||
namespace Aaru.Core.Image;
|
||||
|
||||
public partial class Convert
|
||||
{
|
||||
ErrorNumber SetImageMetadata()
|
||||
{
|
||||
// Builds and applies complete ImageInfo metadata to output image
|
||||
// Copies input metadata and applies command-line overrides (title, comments, creator, drive info, etc.)
|
||||
// Sets Aaru application version and applies all metadata fields to output format
|
||||
|
||||
var imageInfo = new CommonTypes.Structs.ImageInfo
|
||||
{
|
||||
Application = "Aaru",
|
||||
ApplicationVersion = Version.GetInformationalVersion(),
|
||||
Comments = _comments ?? _inputImage.Info.Comments,
|
||||
Creator = _creator ?? _inputImage.Info.Creator,
|
||||
DriveFirmwareRevision = _driveFirmwareRevision ?? _inputImage.Info.DriveFirmwareRevision,
|
||||
DriveManufacturer = _driveManufacturer ?? _inputImage.Info.DriveManufacturer,
|
||||
DriveModel = _driveModel ?? _inputImage.Info.DriveModel,
|
||||
DriveSerialNumber = _driveSerialNumber ?? _inputImage.Info.DriveSerialNumber,
|
||||
LastMediaSequence = _lastMediaSequence != 0 ? _lastMediaSequence : _inputImage.Info.LastMediaSequence,
|
||||
MediaBarcode = _mediaBarcode ?? _inputImage.Info.MediaBarcode,
|
||||
MediaManufacturer = _mediaManufacturer ?? _inputImage.Info.MediaManufacturer,
|
||||
MediaModel = _mediaModel ?? _inputImage.Info.MediaModel,
|
||||
MediaPartNumber = _mediaPartNumber ?? _inputImage.Info.MediaPartNumber,
|
||||
MediaSequence = _mediaSequence != 0 ? _mediaSequence : _inputImage.Info.MediaSequence,
|
||||
MediaSerialNumber = _mediaSerialNumber ?? _inputImage.Info.MediaSerialNumber,
|
||||
MediaTitle = _mediaTitle ?? _inputImage.Info.MediaTitle
|
||||
};
|
||||
|
||||
if(_outputImage.SetImageInfo(imageInfo)) return ErrorNumber.NoError;
|
||||
|
||||
if(!_force)
|
||||
{
|
||||
StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_setting_metadata_not_continuing,
|
||||
_outputImage.ErrorMessage));
|
||||
|
||||
return ErrorNumber.WriteError;
|
||||
}
|
||||
|
||||
ErrorMessage?.Invoke(string.Format(Localization.Core.Error_0_setting_metadata, _outputImage.ErrorMessage));
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
}
|
||||
751
Aaru.Core/Image/Convert/Optical.cs
Normal file
751
Aaru.Core/Image/Convert/Optical.cs
Normal file
@@ -0,0 +1,751 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Aaru.CommonTypes;
|
||||
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;
|
||||
|
||||
namespace Aaru.Core.Image;
|
||||
|
||||
public partial class Convert
|
||||
{
|
||||
ErrorNumber ConvertOptical(IOpticalMediaImage inputOptical, IWritableOpticalImage outputOptical, bool useLong)
|
||||
{
|
||||
if(!outputOptical.SetTracks(inputOptical.Tracks))
|
||||
{
|
||||
StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_sending_tracks_list_to_output_image,
|
||||
outputOptical.ErrorMessage));
|
||||
|
||||
return ErrorNumber.WriteError;
|
||||
}
|
||||
|
||||
if(_decrypt) UpdateStatus?.Invoke("Decrypting encrypted sectors.");
|
||||
|
||||
// Convert all sectors track by track
|
||||
ErrorNumber errno = ConvertOpticalSectors(inputOptical, outputOptical, useLong);
|
||||
|
||||
if(errno != ErrorNumber.NoError) return errno;
|
||||
|
||||
Dictionary<byte, string> isrcs = new();
|
||||
Dictionary<byte, byte> trackFlags = new();
|
||||
string mcn = null;
|
||||
HashSet<int> subchannelExtents = [];
|
||||
Dictionary<byte, int> smallestPregapLbaPerTrack = new();
|
||||
var tracks = new Track[inputOptical.Tracks.Count];
|
||||
|
||||
for(var i = 0; i < tracks.Length; i++)
|
||||
{
|
||||
tracks[i] = new Track
|
||||
{
|
||||
Indexes = new Dictionary<ushort, int>(),
|
||||
Description = inputOptical.Tracks[i].Description,
|
||||
EndSector = inputOptical.Tracks[i].EndSector,
|
||||
StartSector = inputOptical.Tracks[i].StartSector,
|
||||
Pregap = inputOptical.Tracks[i].Pregap,
|
||||
Sequence = inputOptical.Tracks[i].Sequence,
|
||||
Session = inputOptical.Tracks[i].Session,
|
||||
BytesPerSector = inputOptical.Tracks[i].BytesPerSector,
|
||||
RawBytesPerSector = inputOptical.Tracks[i].RawBytesPerSector,
|
||||
Type = inputOptical.Tracks[i].Type,
|
||||
SubchannelType = inputOptical.Tracks[i].SubchannelType
|
||||
};
|
||||
|
||||
foreach(KeyValuePair<ushort, int> idx in inputOptical.Tracks[i].Indexes)
|
||||
tracks[i].Indexes[idx.Key] = idx.Value;
|
||||
}
|
||||
|
||||
// Gets tracks ISRCs
|
||||
foreach(SectorTagType tag in inputOptical.Info.ReadableSectorTags
|
||||
.Where(static t => t == SectorTagType.CdTrackIsrc)
|
||||
.Order())
|
||||
{
|
||||
foreach(Track track in tracks)
|
||||
{
|
||||
errno = inputOptical.ReadSectorTag(track.Sequence, false, tag, out byte[] isrc);
|
||||
|
||||
if(errno != ErrorNumber.NoError) continue;
|
||||
|
||||
isrcs[(byte)track.Sequence] = Encoding.UTF8.GetString(isrc);
|
||||
}
|
||||
}
|
||||
|
||||
// Gets tracks flags
|
||||
foreach(SectorTagType tag in inputOptical.Info.ReadableSectorTags
|
||||
.Where(static t => t == SectorTagType.CdTrackFlags)
|
||||
.Order())
|
||||
{
|
||||
foreach(Track track in tracks)
|
||||
{
|
||||
errno = inputOptical.ReadSectorTag(track.Sequence, false, tag, out byte[] flags);
|
||||
|
||||
if(errno != ErrorNumber.NoError) continue;
|
||||
|
||||
trackFlags[(byte)track.Sequence] = flags[0];
|
||||
}
|
||||
}
|
||||
|
||||
// Gets subchannel extents
|
||||
for(ulong s = 0; s < inputOptical.Info.Sectors; s++)
|
||||
{
|
||||
if(s > int.MaxValue) break;
|
||||
|
||||
subchannelExtents.Add((int)s);
|
||||
}
|
||||
|
||||
errno = ConvertOpticalSectorsTags(inputOptical,
|
||||
outputOptical,
|
||||
useLong,
|
||||
isrcs,
|
||||
ref mcn,
|
||||
tracks,
|
||||
subchannelExtents,
|
||||
smallestPregapLbaPerTrack);
|
||||
|
||||
if(errno != ErrorNumber.NoError) return errno;
|
||||
|
||||
// Write ISRCs
|
||||
foreach(KeyValuePair<byte, string> isrc in isrcs)
|
||||
{
|
||||
outputOptical.WriteSectorTag(Encoding.UTF8.GetBytes(isrc.Value),
|
||||
isrc.Key,
|
||||
false,
|
||||
SectorTagType.CdTrackIsrc);
|
||||
}
|
||||
|
||||
// Write track flags
|
||||
if(trackFlags.Count > 0)
|
||||
{
|
||||
foreach((byte track, byte flags) in trackFlags)
|
||||
outputOptical.WriteSectorTag([flags], track, false, SectorTagType.CdTrackFlags);
|
||||
}
|
||||
|
||||
// Write MCN
|
||||
if(mcn != null) outputOptical.WriteMediaTag(Encoding.UTF8.GetBytes(mcn), MediaTagType.CD_MCN);
|
||||
|
||||
if(!IsCompactDiscMedia(inputOptical.Info.MediaType) || !_generateSubchannels) return ErrorNumber.NoError;
|
||||
|
||||
// Generate subchannel data
|
||||
CompactDisc.GenerateSubchannels(subchannelExtents,
|
||||
tracks,
|
||||
trackFlags,
|
||||
inputOptical.Info.Sectors,
|
||||
null,
|
||||
InitProgress,
|
||||
UpdateProgress,
|
||||
EndProgress,
|
||||
outputOptical);
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
|
||||
ErrorNumber ConvertOpticalSectors(IOpticalMediaImage inputOptical, IWritableOpticalImage outputOptical,
|
||||
bool useLong)
|
||||
{
|
||||
InitProgress?.Invoke();
|
||||
InitProgress2?.Invoke();
|
||||
byte[] generatedTitleKeys = null;
|
||||
var currentTrack = 0;
|
||||
|
||||
foreach(Track track in inputOptical.Tracks)
|
||||
{
|
||||
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)
|
||||
{
|
||||
byte[] sector;
|
||||
|
||||
uint sectorsToDo;
|
||||
|
||||
if(trackSectors - doneSectors >= _count)
|
||||
sectorsToDo = _count;
|
||||
else
|
||||
sectorsToDo = (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);
|
||||
|
||||
var useNotLong = false;
|
||||
var result = false;
|
||||
SectorStatus sectorStatus = SectorStatus.NotDumped;
|
||||
var 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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
if(!result && sector.Length % 2352 != 0)
|
||||
{
|
||||
if(!_force)
|
||||
{
|
||||
StoppingErrorMessage
|
||||
?.Invoke(UI.Input_image_is_not_returning_raw_sectors_use_force_if_you_want_to_continue);
|
||||
|
||||
return ErrorNumber.InOutError;
|
||||
}
|
||||
|
||||
useNotLong = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(!useLong || useNotLong)
|
||||
{
|
||||
errno = sectorsToDo == 1
|
||||
? inputOptical.ReadSector(doneSectors + track.StartSector,
|
||||
false,
|
||||
out sector,
|
||||
out sectorStatus)
|
||||
: inputOptical.ReadSectors(doneSectors + track.StartSector,
|
||||
false,
|
||||
sectorsToDo,
|
||||
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
|
||||
? 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;
|
||||
}
|
||||
|
||||
ErrorNumber ConvertOpticalSectorsTags(IOpticalMediaImage inputOptical, IWritableOpticalImage outputOptical,
|
||||
bool useLong, Dictionary<byte, string> isrcs, ref string mcn, Track[] tracks,
|
||||
HashSet<int> subchannelExtents,
|
||||
Dictionary<byte, int> smallestPregapLbaPerTrack)
|
||||
{
|
||||
foreach(SectorTagType tag in inputOptical.Info.ReadableSectorTags.Order().TakeWhile(_ => useLong))
|
||||
{
|
||||
switch(tag)
|
||||
{
|
||||
case SectorTagType.AppleSonyTag:
|
||||
case SectorTagType.AppleProfileTag:
|
||||
case SectorTagType.PriamDataTowerTag:
|
||||
case SectorTagType.CdSectorSync:
|
||||
case SectorTagType.CdSectorHeader:
|
||||
case SectorTagType.CdSectorSubHeader:
|
||||
case SectorTagType.CdSectorEdc:
|
||||
case SectorTagType.CdSectorEccP:
|
||||
case SectorTagType.CdSectorEccQ:
|
||||
case SectorTagType.CdSectorEcc:
|
||||
case SectorTagType.DvdSectorCmi:
|
||||
case SectorTagType.DvdSectorTitleKey:
|
||||
case SectorTagType.DvdSectorEdc:
|
||||
case SectorTagType.DvdSectorIed:
|
||||
case SectorTagType.DvdSectorInformation:
|
||||
case SectorTagType.DvdSectorNumber:
|
||||
// This tags are inline in long sector
|
||||
continue;
|
||||
}
|
||||
|
||||
if(_force && !outputOptical.SupportedSectorTags.Contains(tag)) continue;
|
||||
|
||||
ErrorNumber errno = ErrorNumber.NoError;
|
||||
|
||||
InitProgress?.Invoke();
|
||||
InitProgress2?.Invoke();
|
||||
var currentTrack = 0;
|
||||
|
||||
foreach(Track track in inputOptical.Tracks)
|
||||
{
|
||||
UpdateProgress?.Invoke(string.Format(UI.Converting_tags_in_track_0_of_1,
|
||||
currentTrack + 1,
|
||||
inputOptical.Tracks.Count),
|
||||
currentTrack,
|
||||
inputOptical.Tracks.Count);
|
||||
|
||||
ulong doneSectors = 0;
|
||||
ulong trackSectors = track.EndSector - track.StartSector + 1;
|
||||
byte[] sector;
|
||||
bool result;
|
||||
|
||||
switch(tag)
|
||||
{
|
||||
case SectorTagType.CdTrackFlags:
|
||||
case SectorTagType.CdTrackIsrc:
|
||||
errno = inputOptical.ReadSectorTag(track.Sequence, false, tag, out sector);
|
||||
|
||||
switch(errno)
|
||||
{
|
||||
case ErrorNumber.NoData:
|
||||
errno = ErrorNumber.NoError;
|
||||
|
||||
continue;
|
||||
case ErrorNumber.NoError:
|
||||
result = outputOptical.WriteSectorTag(sector, track.Sequence, false, tag);
|
||||
|
||||
break;
|
||||
default:
|
||||
{
|
||||
if(_force)
|
||||
{
|
||||
ErrorMessage?.Invoke(string.Format(UI.Error_0_writing_tag_continuing,
|
||||
outputOptical.ErrorMessage));
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_writing_tag_not_continuing,
|
||||
outputOptical.ErrorMessage));
|
||||
|
||||
return ErrorNumber.WriteError;
|
||||
}
|
||||
}
|
||||
|
||||
if(!result)
|
||||
{
|
||||
if(_force)
|
||||
{
|
||||
ErrorMessage?.Invoke(string.Format(UI.Error_0_writing_tag_continuing,
|
||||
outputOptical.ErrorMessage));
|
||||
}
|
||||
else
|
||||
{
|
||||
StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_writing_tag_not_continuing,
|
||||
outputOptical.ErrorMessage));
|
||||
|
||||
return ErrorNumber.WriteError;
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
while(doneSectors < trackSectors)
|
||||
{
|
||||
uint sectorsToDo;
|
||||
|
||||
if(trackSectors - doneSectors >= _count)
|
||||
sectorsToDo = _count;
|
||||
else
|
||||
sectorsToDo = (uint)(trackSectors - doneSectors);
|
||||
|
||||
UpdateProgress2?.Invoke(string.Format(UI.Converting_tag_3_for_sectors_0_to_1_in_track_2,
|
||||
doneSectors + track.StartSector,
|
||||
doneSectors + sectorsToDo + track.StartSector,
|
||||
track.Sequence,
|
||||
tag),
|
||||
(long)(doneSectors + track.StartSector),
|
||||
(long)(doneSectors + sectorsToDo + track.StartSector));
|
||||
|
||||
if(sectorsToDo == 1)
|
||||
{
|
||||
errno = inputOptical.ReadSectorTag(doneSectors + track.StartSector, false, tag, out sector);
|
||||
|
||||
if(errno == ErrorNumber.NoError)
|
||||
{
|
||||
if(tag == SectorTagType.CdSectorSubchannel)
|
||||
{
|
||||
bool indexesChanged = CompactDisc.WriteSubchannelToImage(MmcSubchannel.Raw,
|
||||
MmcSubchannel.Raw,
|
||||
sector,
|
||||
doneSectors + track.StartSector,
|
||||
1,
|
||||
null,
|
||||
isrcs,
|
||||
(byte)track.Sequence,
|
||||
ref mcn,
|
||||
tracks,
|
||||
subchannelExtents,
|
||||
_fixSubchannelPosition,
|
||||
outputOptical,
|
||||
_fixSubchannel,
|
||||
_fixSubchannelCrc,
|
||||
null,
|
||||
smallestPregapLbaPerTrack,
|
||||
false,
|
||||
out _);
|
||||
|
||||
if(indexesChanged) outputOptical.SetTracks(tracks.ToList());
|
||||
|
||||
result = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
result = outputOptical.WriteSectorTag(sector,
|
||||
doneSectors + track.StartSector,
|
||||
false,
|
||||
tag);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
result = true;
|
||||
|
||||
if(_force)
|
||||
{
|
||||
ErrorMessage?.Invoke(string.Format(UI.Error_0_reading_tag_for_sector_1_continuing,
|
||||
errno,
|
||||
doneSectors + track.StartSector));
|
||||
}
|
||||
else
|
||||
{
|
||||
StoppingErrorMessage
|
||||
?.Invoke(string.Format(UI.Error_0_reading_tag_for_sector_1_not_continuing,
|
||||
errno,
|
||||
doneSectors + track.StartSector));
|
||||
|
||||
return errno;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
errno = inputOptical.ReadSectorsTag(doneSectors + track.StartSector,
|
||||
false,
|
||||
sectorsToDo,
|
||||
tag,
|
||||
out sector);
|
||||
|
||||
if(errno == ErrorNumber.NoError)
|
||||
{
|
||||
if(tag == SectorTagType.CdSectorSubchannel)
|
||||
{
|
||||
bool indexesChanged = CompactDisc.WriteSubchannelToImage(MmcSubchannel.Raw,
|
||||
MmcSubchannel.Raw,
|
||||
sector,
|
||||
doneSectors + track.StartSector,
|
||||
sectorsToDo,
|
||||
null,
|
||||
isrcs,
|
||||
(byte)track.Sequence,
|
||||
ref mcn,
|
||||
tracks,
|
||||
subchannelExtents,
|
||||
_fixSubchannelPosition,
|
||||
outputOptical,
|
||||
_fixSubchannel,
|
||||
_fixSubchannelCrc,
|
||||
null,
|
||||
smallestPregapLbaPerTrack,
|
||||
false,
|
||||
out _);
|
||||
|
||||
if(indexesChanged) outputOptical.SetTracks(tracks.ToList());
|
||||
|
||||
result = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
result = outputOptical.WriteSectorsTag(sector,
|
||||
doneSectors + track.StartSector,
|
||||
false,
|
||||
sectorsToDo,
|
||||
tag);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
result = true;
|
||||
|
||||
if(_force)
|
||||
{
|
||||
ErrorMessage?.Invoke(string.Format(UI.Error_0_reading_tag_for_sector_1_continuing,
|
||||
errno,
|
||||
doneSectors + track.StartSector));
|
||||
}
|
||||
else
|
||||
{
|
||||
StoppingErrorMessage
|
||||
?.Invoke(string.Format(UI.Error_0_reading_tag_for_sector_1_not_continuing,
|
||||
errno,
|
||||
doneSectors + track.StartSector));
|
||||
|
||||
return errno;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!result)
|
||||
{
|
||||
if(_force)
|
||||
{
|
||||
ErrorMessage?.Invoke(string.Format(UI.Error_0_writing_tag_for_sector_1_continuing,
|
||||
outputOptical.ErrorMessage,
|
||||
doneSectors + track.StartSector));
|
||||
}
|
||||
else
|
||||
{
|
||||
StoppingErrorMessage
|
||||
?.Invoke(string.Format(UI.Error_0_writing_tag_for_sector_1_not_continuing,
|
||||
outputOptical.ErrorMessage,
|
||||
doneSectors + track.StartSector));
|
||||
|
||||
return ErrorNumber.WriteError;
|
||||
}
|
||||
}
|
||||
|
||||
doneSectors += sectorsToDo;
|
||||
}
|
||||
|
||||
currentTrack++;
|
||||
}
|
||||
|
||||
EndProgress?.Invoke();
|
||||
EndProgress2?.Invoke();
|
||||
|
||||
if(errno != ErrorNumber.NoError && !_force) return errno;
|
||||
}
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
|
||||
void DecryptDvdSector(ref byte[] sector, IOpticalMediaImage inputOptical, ulong sectorAddress, uint sectorsToDo,
|
||||
PluginRegister plugins, ref byte[] generatedTitleKeys)
|
||||
{
|
||||
// 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
|
||||
|
||||
// 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)
|
||||
{
|
||||
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;
|
||||
|
||||
private bool IsCompactDiscMedia(MediaType mediaType) =>
|
||||
|
||||
// Checks if media type is any variant of compact disc (CD, CDDA, CDR, CDRW, etc.)
|
||||
// Covers all 45+ CD-based media types including gaming and specialty formats
|
||||
mediaType is MediaType.CD
|
||||
or MediaType.CDDA
|
||||
or MediaType.CDG
|
||||
or MediaType.CDEG
|
||||
or MediaType.CDI
|
||||
or MediaType.CDROM
|
||||
or MediaType.CDROMXA
|
||||
or MediaType.CDPLUS
|
||||
or MediaType.CDMO
|
||||
or MediaType.CDR
|
||||
or MediaType.CDRW
|
||||
or MediaType.CDMRW
|
||||
or MediaType.VCD
|
||||
or MediaType.SVCD
|
||||
or MediaType.PCD
|
||||
or MediaType.DTSCD
|
||||
or MediaType.CDMIDI
|
||||
or MediaType.CDV
|
||||
or MediaType.CDIREADY
|
||||
or MediaType.FMTOWNS
|
||||
or MediaType.PS1CD
|
||||
or MediaType.PS2CD
|
||||
or MediaType.MEGACD
|
||||
or MediaType.SATURNCD
|
||||
or MediaType.GDROM
|
||||
or MediaType.GDR
|
||||
or MediaType.MilCD
|
||||
or MediaType.SuperCDROM2
|
||||
or MediaType.JaguarCD
|
||||
or MediaType.ThreeDO
|
||||
or MediaType.PCFX
|
||||
or MediaType.NeoGeoCD
|
||||
or MediaType.CDTV
|
||||
or MediaType.CD32
|
||||
or MediaType.Playdia
|
||||
or MediaType.Pippin
|
||||
or MediaType.VideoNow
|
||||
or MediaType.VideoNowColor
|
||||
or MediaType.VideoNowXp
|
||||
or MediaType.CVD;
|
||||
}
|
||||
229
Aaru.Core/Image/Convert/Sectors.cs
Normal file
229
Aaru.Core/Image/Convert/Sectors.cs
Normal file
@@ -0,0 +1,229 @@
|
||||
using System.Linq;
|
||||
using Aaru.CommonTypes.Enums;
|
||||
using Aaru.Localization;
|
||||
using Aaru.Logging;
|
||||
|
||||
namespace Aaru.Core.Image;
|
||||
|
||||
public partial class Convert
|
||||
{
|
||||
ErrorNumber ConvertSectors(bool useLong, bool isTape)
|
||||
{
|
||||
InitProgress?.Invoke();
|
||||
ErrorNumber errno = ErrorNumber.NoError;
|
||||
ulong doneSectors = 0;
|
||||
|
||||
while(doneSectors < _inputImage.Info.Sectors)
|
||||
{
|
||||
byte[] sector;
|
||||
|
||||
uint sectorsToDo;
|
||||
|
||||
if(isTape)
|
||||
sectorsToDo = 1;
|
||||
else if(_inputImage.Info.Sectors - doneSectors >= _count)
|
||||
sectorsToDo = _count;
|
||||
else
|
||||
sectorsToDo = (uint)(_inputImage.Info.Sectors - doneSectors);
|
||||
|
||||
UpdateProgress?.Invoke(string.Format(UI.Converting_sectors_0_to_1, doneSectors, doneSectors + sectorsToDo),
|
||||
(long)doneSectors,
|
||||
(long)_inputImage.Info.Sectors);
|
||||
|
||||
bool result;
|
||||
SectorStatus sectorStatus = SectorStatus.NotDumped;
|
||||
var sectorStatusArray = new SectorStatus[1];
|
||||
|
||||
if(useLong)
|
||||
{
|
||||
errno = sectorsToDo == 1
|
||||
? _inputImage.ReadSectorLong(doneSectors, false, out sector, out sectorStatus)
|
||||
: _inputImage.ReadSectorsLong(doneSectors,
|
||||
false,
|
||||
sectorsToDo,
|
||||
out sector,
|
||||
out sectorStatusArray);
|
||||
|
||||
if(errno == ErrorNumber.NoError)
|
||||
{
|
||||
result = sectorsToDo == 1
|
||||
? _outputImage.WriteSectorLong(sector, doneSectors, false, sectorStatus)
|
||||
: _outputImage.WriteSectorsLong(sector,
|
||||
doneSectors,
|
||||
false,
|
||||
sectorsToDo,
|
||||
sectorStatusArray);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = true;
|
||||
|
||||
if(_force)
|
||||
ErrorMessage?.Invoke(string.Format(UI.Error_0_reading_sector_1_continuing, errno, doneSectors));
|
||||
else
|
||||
{
|
||||
StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_reading_sector_1_not_continuing,
|
||||
errno,
|
||||
doneSectors));
|
||||
|
||||
return errno;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
errno = sectorsToDo == 1
|
||||
? _inputImage.ReadSector(doneSectors, false, out sector, out sectorStatus)
|
||||
: _inputImage.ReadSectors(doneSectors,
|
||||
false,
|
||||
sectorsToDo,
|
||||
out sector,
|
||||
out sectorStatusArray);
|
||||
|
||||
if(errno == ErrorNumber.NoError)
|
||||
{
|
||||
result = sectorsToDo == 1
|
||||
? _outputImage.WriteSector(sector, doneSectors, false, sectorStatus)
|
||||
: _outputImage.WriteSectors(sector,
|
||||
doneSectors,
|
||||
false,
|
||||
sectorsToDo,
|
||||
sectorStatusArray);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = true;
|
||||
|
||||
if(_force)
|
||||
ErrorMessage?.Invoke(string.Format(UI.Error_0_reading_sector_1_continuing, errno, doneSectors));
|
||||
else
|
||||
{
|
||||
StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_reading_sector_1_not_continuing,
|
||||
errno,
|
||||
doneSectors));
|
||||
|
||||
return errno;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!result)
|
||||
{
|
||||
if(_force)
|
||||
{
|
||||
ErrorMessage?.Invoke(string.Format(UI.Error_0_writing_sector_1_continuing,
|
||||
_outputImage.ErrorMessage,
|
||||
doneSectors));
|
||||
}
|
||||
else
|
||||
{
|
||||
StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_writing_sector_1_not_continuing,
|
||||
_outputImage.ErrorMessage,
|
||||
doneSectors));
|
||||
|
||||
return ErrorNumber.WriteError;
|
||||
}
|
||||
}
|
||||
|
||||
doneSectors += sectorsToDo;
|
||||
}
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
|
||||
ErrorNumber ConvertSectorsTags(bool useLong)
|
||||
{
|
||||
foreach(SectorTagType tag in _inputImage.Info.ReadableSectorTags.TakeWhile(_ => useLong))
|
||||
{
|
||||
switch(tag)
|
||||
{
|
||||
case SectorTagType.AppleSonyTag:
|
||||
case SectorTagType.AppleProfileTag:
|
||||
case SectorTagType.PriamDataTowerTag:
|
||||
case SectorTagType.CdSectorSync:
|
||||
case SectorTagType.CdSectorHeader:
|
||||
case SectorTagType.CdSectorSubHeader:
|
||||
case SectorTagType.CdSectorEdc:
|
||||
case SectorTagType.CdSectorEccP:
|
||||
case SectorTagType.CdSectorEccQ:
|
||||
case SectorTagType.CdSectorEcc:
|
||||
// These tags are inline in long sector
|
||||
continue;
|
||||
}
|
||||
|
||||
if(_force && !_outputImage.SupportedSectorTags.Contains(tag)) continue;
|
||||
|
||||
ulong doneSectors = 0;
|
||||
ErrorNumber errno;
|
||||
|
||||
InitProgress?.Invoke();
|
||||
|
||||
while(doneSectors < _inputImage.Info.Sectors)
|
||||
{
|
||||
uint sectorsToDo;
|
||||
|
||||
if(_inputImage.Info.Sectors - doneSectors >= _count)
|
||||
sectorsToDo = _count;
|
||||
else
|
||||
sectorsToDo = (uint)(_inputImage.Info.Sectors - doneSectors);
|
||||
|
||||
UpdateProgress?.Invoke(string.Format(UI.Converting_tag_2_for_sectors_0_to_1,
|
||||
doneSectors,
|
||||
doneSectors + sectorsToDo,
|
||||
tag),
|
||||
(long)doneSectors,
|
||||
(long)_inputImage.Info.Sectors);
|
||||
|
||||
bool result;
|
||||
|
||||
errno = sectorsToDo == 1
|
||||
? _inputImage.ReadSectorTag(doneSectors, false, tag, out byte[] sector)
|
||||
: _inputImage.ReadSectorsTag(doneSectors, false, sectorsToDo, tag, out sector);
|
||||
|
||||
if(errno == ErrorNumber.NoError)
|
||||
{
|
||||
result = sectorsToDo == 1
|
||||
? _outputImage.WriteSectorTag(sector, doneSectors, false, tag)
|
||||
: _outputImage.WriteSectorsTag(sector, doneSectors, false, sectorsToDo, tag);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = true;
|
||||
|
||||
if(_force)
|
||||
AaruLogging.Error(UI.Error_0_reading_sector_1_continuing, errno, doneSectors);
|
||||
else
|
||||
{
|
||||
AaruLogging.Error(UI.Error_0_reading_sector_1_not_continuing, errno, doneSectors);
|
||||
|
||||
return errno;
|
||||
}
|
||||
}
|
||||
|
||||
if(!result)
|
||||
{
|
||||
if(_force)
|
||||
{
|
||||
AaruLogging.Error(UI.Error_0_writing_sector_1_continuing,
|
||||
_outputImage.ErrorMessage,
|
||||
doneSectors);
|
||||
}
|
||||
else
|
||||
{
|
||||
AaruLogging.Error(UI.Error_0_writing_sector_1_not_continuing,
|
||||
_outputImage.ErrorMessage,
|
||||
doneSectors);
|
||||
|
||||
return ErrorNumber.WriteError;
|
||||
}
|
||||
}
|
||||
|
||||
doneSectors += sectorsToDo;
|
||||
}
|
||||
|
||||
EndProgress?.Invoke();
|
||||
}
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
}
|
||||
63
Aaru.Core/Image/Convert/Tags.cs
Normal file
63
Aaru.Core/Image/Convert/Tags.cs
Normal file
@@ -0,0 +1,63 @@
|
||||
using System.Linq;
|
||||
using Aaru.CommonTypes.Enums;
|
||||
using Aaru.Localization;
|
||||
using Aaru.Logging;
|
||||
using Humanizer;
|
||||
|
||||
namespace Aaru.Core.Image;
|
||||
|
||||
public partial class Convert
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts media tags (TOC, lead-in, etc.) from input to output format
|
||||
/// Handles force mode to skip unsupported tags or fail on data loss
|
||||
/// Shows progress for each tag being converted
|
||||
/// </summary>
|
||||
/// <returns>Status code</returns>
|
||||
ErrorNumber ConvertMediaTags()
|
||||
{
|
||||
InitProgress?.Invoke();
|
||||
ErrorNumber errorNumber = ErrorNumber.NoError;
|
||||
|
||||
foreach(MediaTagType mediaTag in _inputImage.Info.ReadableMediaTags.Where(mediaTag => !_force ||
|
||||
_outputImage.SupportedMediaTags.Contains(mediaTag)))
|
||||
{
|
||||
PulseProgress?.Invoke(string.Format(UI.Converting_media_tag_0, mediaTag.Humanize()));
|
||||
ErrorNumber errno = _inputImage.ReadMediaTag(mediaTag, out byte[] tag);
|
||||
|
||||
if(errno != ErrorNumber.NoError)
|
||||
{
|
||||
if(_force)
|
||||
ErrorMessage?.Invoke(string.Format(UI.Error_0_reading_media_tag, errno));
|
||||
else
|
||||
{
|
||||
AaruLogging.Error(UI.Error_0_reading_media_tag_not_continuing, errno);
|
||||
|
||||
errorNumber = errno;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if(_outputImage?.WriteMediaTag(tag, mediaTag) == true) continue;
|
||||
|
||||
if(_force)
|
||||
ErrorMessage?.Invoke(string.Format(UI.Error_0_writing_media_tag, _outputImage?.ErrorMessage));
|
||||
else
|
||||
{
|
||||
StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_writing_media_tag_not_continuing,
|
||||
_outputImage?.ErrorMessage));
|
||||
|
||||
errorNumber = ErrorNumber.WriteError;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
EndProgress?.Invoke();
|
||||
|
||||
return errorNumber;
|
||||
}
|
||||
}
|
||||
42
Aaru.Core/Image/Convert/Tape.cs
Normal file
42
Aaru.Core/Image/Convert/Tape.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using Aaru.CommonTypes.Enums;
|
||||
using Aaru.CommonTypes.Interfaces;
|
||||
using Aaru.Localization;
|
||||
|
||||
namespace Aaru.Core.Image;
|
||||
|
||||
public partial class Convert
|
||||
{
|
||||
ErrorNumber ValidateTapeImage(ITapeImage inputTape, IWritableTapeImage outputTape)
|
||||
{
|
||||
// Validates tape image format compatibility
|
||||
// Checks if input is tape-based but output format doesn't support tape images
|
||||
// Returns error if unsupported media type combination detected
|
||||
|
||||
if(inputTape?.IsTape != true || outputTape is not null) return ErrorNumber.NoError;
|
||||
|
||||
StoppingErrorMessage?.Invoke(UI.Input_format_contains_a_tape_image_and_is_not_supported_by_output_format);
|
||||
|
||||
return ErrorNumber.UnsupportedMedia;
|
||||
}
|
||||
|
||||
ErrorNumber SetupTapeImage(ITapeImage inputTape, IWritableTapeImage outputTape)
|
||||
{
|
||||
// Configures output format for tape image handling
|
||||
// Calls SetTape() on output to initialize tape mode if both input and output support tapes
|
||||
// Returns error if tape mode initialization fails
|
||||
|
||||
if(inputTape?.IsTape != true || outputTape == null) return ErrorNumber.NoError;
|
||||
|
||||
bool ret = outputTape.SetTape();
|
||||
|
||||
// Cannot set image to tape mode
|
||||
if(ret) return ErrorNumber.NoError;
|
||||
|
||||
StoppingErrorMessage?.Invoke(UI.Error_setting_output_image_in_tape_mode +
|
||||
Environment.NewLine +
|
||||
_outputImage.ErrorMessage);
|
||||
|
||||
return ErrorNumber.WriteError;
|
||||
}
|
||||
}
|
||||
@@ -63,7 +63,7 @@ public sealed partial class Sidecar
|
||||
new AudioMedia
|
||||
{
|
||||
Checksums = imgChecksums,
|
||||
Image = new Image
|
||||
Image = new CommonTypes.AaruMetadata.Image
|
||||
{
|
||||
Format = image.Format,
|
||||
Offset = 0,
|
||||
|
||||
@@ -75,7 +75,7 @@ public sealed partial class Sidecar
|
||||
new BlockMedia
|
||||
{
|
||||
Checksums = imgChecksums,
|
||||
Image = new Image
|
||||
Image = new CommonTypes.AaruMetadata.Image
|
||||
{
|
||||
Format = image.Format,
|
||||
Offset = 0,
|
||||
@@ -1013,7 +1013,7 @@ public sealed partial class Sidecar
|
||||
{
|
||||
Cylinder = t / image.Info.Heads,
|
||||
Head = (ushort)(t % image.Info.Heads),
|
||||
Image = new Image
|
||||
Image = new CommonTypes.AaruMetadata.Image
|
||||
{
|
||||
Format = scpImage.Format,
|
||||
Value = Path.GetFileName(scpFilePath),
|
||||
@@ -1126,7 +1126,7 @@ public sealed partial class Sidecar
|
||||
{
|
||||
Cylinder = kvp.Key / image.Info.Heads,
|
||||
Head = (ushort)(kvp.Key % image.Info.Heads),
|
||||
Image = new Image
|
||||
Image = new CommonTypes.AaruMetadata.Image
|
||||
{
|
||||
Format = kfImage.Format,
|
||||
Value = kfDir
|
||||
@@ -1220,7 +1220,7 @@ public sealed partial class Sidecar
|
||||
{
|
||||
Cylinder = (uint)(t / image.Info.Heads),
|
||||
Head = (ushort)(t % image.Info.Heads),
|
||||
Image = new Image
|
||||
Image = new CommonTypes.AaruMetadata.Image
|
||||
{
|
||||
Format = dfiImage.Format,
|
||||
Value = Path.GetFileName(dfiFilePath)
|
||||
|
||||
@@ -52,7 +52,7 @@ public sealed partial class Sidecar
|
||||
[
|
||||
new BlockMedia
|
||||
{
|
||||
Image = new Image
|
||||
Image = new CommonTypes.AaruMetadata.Image
|
||||
{
|
||||
Format = "Directory",
|
||||
Value = folderName
|
||||
@@ -69,7 +69,7 @@ public sealed partial class Sidecar
|
||||
[
|
||||
new TapePartition
|
||||
{
|
||||
Image = new Image
|
||||
Image = new CommonTypes.AaruMetadata.Image
|
||||
{
|
||||
Format = "Directory",
|
||||
Value = folderName
|
||||
@@ -98,7 +98,7 @@ public sealed partial class Sidecar
|
||||
|
||||
var tapeFile = new TapeFile
|
||||
{
|
||||
Image = new Image
|
||||
Image = new CommonTypes.AaruMetadata.Image
|
||||
{
|
||||
Format = "Raw disk image (sector by sector copy)",
|
||||
Offset = 0,
|
||||
|
||||
@@ -61,12 +61,14 @@ public sealed partial class Sidecar
|
||||
new LinearMedia
|
||||
{
|
||||
Checksums = imgChecksums,
|
||||
Image = new Image
|
||||
{
|
||||
Format = image.Format,
|
||||
Offset = 0,
|
||||
Value = Path.GetFileName(imagePath)
|
||||
},
|
||||
Image =
|
||||
new CommonTypes.AaruMetadata.Image
|
||||
{
|
||||
Format = image.Format,
|
||||
Offset = 0,
|
||||
Value =
|
||||
Path.GetFileName(imagePath)
|
||||
},
|
||||
Size = image.Info.Sectors
|
||||
}
|
||||
];
|
||||
|
||||
@@ -72,7 +72,7 @@ public sealed partial class Sidecar
|
||||
new OpticalDisc
|
||||
{
|
||||
Checksums = imgChecksums,
|
||||
Image = new Image
|
||||
Image = new CommonTypes.AaruMetadata.Image
|
||||
{
|
||||
Format = image.Format,
|
||||
Offset = 0,
|
||||
@@ -308,7 +308,7 @@ public sealed partial class Sidecar
|
||||
break;
|
||||
}
|
||||
|
||||
xmlTrk.Image = new Image
|
||||
xmlTrk.Image = new CommonTypes.AaruMetadata.Image
|
||||
{
|
||||
Value = Path.GetFileName(trk.File),
|
||||
Format = trk.FileType
|
||||
@@ -413,7 +413,7 @@ public sealed partial class Sidecar
|
||||
{
|
||||
xmlTrk.SubChannel = new SubChannel
|
||||
{
|
||||
Image = new Image
|
||||
Image = new CommonTypes.AaruMetadata.Image
|
||||
{
|
||||
Value = trk.SubchannelFile
|
||||
},
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user