diff --git a/Aaru.Core/Image/Convert/Capabilities.cs b/Aaru.Core/Image/Convert/Capabilities.cs
new file mode 100644
index 000000000..a34df7a2d
--- /dev/null
+++ b/Aaru.Core/Image/Convert/Capabilities.cs
@@ -0,0 +1,72 @@
+using System;
+using System.Linq;
+using Aaru.CommonTypes.Enums;
+using Aaru.Localization;
+
+namespace Aaru.Core.Image;
+
+public partial class Convert
+{
+ ///
+ /// 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)
+ ///
+ /// Error if required features not supported and data would be lost
+ 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;
+ }
+}
\ No newline at end of file
diff --git a/Aaru.Core/Image/Convert/Convert.cs b/Aaru.Core/Image/Convert/Convert.cs
new file mode 100644
index 000000000..9a02c197f
--- /dev/null
+++ b/Aaru.Core/Image/Convert/Convert.cs
@@ -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 _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 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 = _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;
+ }
+
+ /// Event raised when the progress bar is no longer needed
+ public event EndProgressHandler EndProgress;
+
+ /// Event raised when a progress bar is needed
+ public event InitProgressHandler InitProgress;
+
+ /// Event raised to report status updates
+ public event UpdateStatusHandler UpdateStatus;
+
+ /// Event raised to report a non-fatal error
+ public event ErrorMessageHandler ErrorMessage;
+
+ /// Event raised to report a fatal error that stops the dumping operation and should call user's attention
+ public event ErrorMessageHandler StoppingErrorMessage;
+
+ /// Event raised to update the values of a determinate progress bar
+ public event UpdateProgressHandler UpdateProgress;
+
+ /// Event raised to update the status of an indeterminate progress bar
+ public event PulseProgressHandler PulseProgress;
+
+ /// Event raised when the progress bar is no longer needed
+ public event EndProgressHandler2 EndProgress2;
+
+ /// Event raised when a progress bar is needed
+ public event InitProgressHandler2 InitProgress2;
+
+ /// Event raised to update the values of a determinate progress bar
+ public event UpdateProgressHandler2 UpdateProgress2;
+
+ public void Abort()
+ {
+ _aborted = true;
+ }
+}
\ No newline at end of file
diff --git a/Aaru.Core/Image/Convert/Create.cs b/Aaru.Core/Image/Convert/Create.cs
new file mode 100644
index 000000000..8bcde68e4
--- /dev/null
+++ b/Aaru.Core/Image/Convert/Create.cs
@@ -0,0 +1,35 @@
+using Aaru.CommonTypes.Enums;
+using Aaru.Localization;
+
+namespace Aaru.Core.Image;
+
+public partial class Convert
+{
+ ///
+ /// 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
+ ///
+ /// Error code if creation fails
+ 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;
+ }
+}
\ No newline at end of file
diff --git a/Aaru.Core/Image/Convert/Edge.cs b/Aaru.Core/Image/Convert/Edge.cs
new file mode 100644
index 000000000..13f0fb8e4
--- /dev/null
+++ b/Aaru.Core/Image/Convert/Edge.cs
@@ -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
+{
+ ///
+ /// 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
+ ///
+ /// Error code if conversion fails in non-force mode
+ 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;
+ }
+}
\ No newline at end of file
diff --git a/Aaru.Core/Image/Convert/Flux.cs b/Aaru.Core/Image/Convert/Flux.cs
new file mode 100644
index 000000000..9f0361a5a
--- /dev/null
+++ b/Aaru.Core/Image/Convert/Flux.cs
@@ -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;
+ }
+}
\ No newline at end of file
diff --git a/Aaru.Core/Image/Convert/Metadata.cs b/Aaru.Core/Image/Convert/Metadata.cs
new file mode 100644
index 000000000..111606d8f
--- /dev/null
+++ b/Aaru.Core/Image/Convert/Metadata.cs
@@ -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;
+ }
+}
\ No newline at end of file
diff --git a/Aaru.Core/Image/Convert/Optical.cs b/Aaru.Core/Image/Convert/Optical.cs
new file mode 100644
index 000000000..a4d4ecb8b
--- /dev/null
+++ b/Aaru.Core/Image/Convert/Optical.cs
@@ -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 isrcs = new();
+ Dictionary trackFlags = new();
+ string mcn = null;
+ HashSet subchannelExtents = [];
+ Dictionary smallestPregapLbaPerTrack = new();
+ var tracks = new Track[inputOptical.Tracks.Count];
+
+ for(var i = 0; i < tracks.Length; i++)
+ {
+ tracks[i] = new Track
+ {
+ Indexes = new Dictionary(),
+ 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 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 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 isrcs, ref string mcn, Track[] tracks,
+ HashSet subchannelExtents,
+ Dictionary 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);
+ }
+ }
+ }
+ }
+
+ ///
+ /// Generates DVD CSS title keys from ISO9660 filesystem
+ /// Used when explicit title keys are not available in sector tags
+ /// Searches for ISO9660 partitions to derive decryption keys
+ ///
+ void GenerateDvdTitleKeys(IOpticalMediaImage inputOptical, PluginRegister plugins, ref byte[] generatedTitleKeys)
+ {
+ List partitions = Partitions.GetAll(inputOptical);
+
+ partitions = partitions.FindAll(p =>
+ {
+ Filesystems.Identify(inputOptical, out List idPlugins, p);
+
+ return idPlugins.Contains("iso9660 filesystem");
+ });
+
+ if(!plugins.ReadOnlyFilesystems.TryGetValue("iso9660 filesystem", out IReadOnlyFilesystem rofs)) return;
+
+ AaruLogging.Debug(MODULE_NAME, UI.Generating_decryption_keys);
+
+ generatedTitleKeys = CSS.GenerateTitleKeys(inputOptical, partitions, inputOptical.Info.Sectors, rofs);
+ }
+
+ 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;
+}
\ No newline at end of file
diff --git a/Aaru.Core/Image/Convert/Sectors.cs b/Aaru.Core/Image/Convert/Sectors.cs
new file mode 100644
index 000000000..32f40bae7
--- /dev/null
+++ b/Aaru.Core/Image/Convert/Sectors.cs
@@ -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;
+ }
+}
\ No newline at end of file
diff --git a/Aaru.Core/Image/Convert/Tags.cs b/Aaru.Core/Image/Convert/Tags.cs
new file mode 100644
index 000000000..3089544ee
--- /dev/null
+++ b/Aaru.Core/Image/Convert/Tags.cs
@@ -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
+{
+ ///
+ /// 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
+ ///
+ /// Status code
+ 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;
+ }
+}
\ No newline at end of file
diff --git a/Aaru.Core/Image/Convert/Tape.cs b/Aaru.Core/Image/Convert/Tape.cs
new file mode 100644
index 000000000..f6d3abdb3
--- /dev/null
+++ b/Aaru.Core/Image/Convert/Tape.cs
@@ -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;
+ }
+}
\ No newline at end of file
diff --git a/Aaru.Core/Sidecar/AudioMedia.cs b/Aaru.Core/Sidecar/AudioMedia.cs
index 5b02623e2..702e151bf 100644
--- a/Aaru.Core/Sidecar/AudioMedia.cs
+++ b/Aaru.Core/Sidecar/AudioMedia.cs
@@ -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,
diff --git a/Aaru.Core/Sidecar/BlockMedia.cs b/Aaru.Core/Sidecar/BlockMedia.cs
index e72c73c0b..3dd164151 100644
--- a/Aaru.Core/Sidecar/BlockMedia.cs
+++ b/Aaru.Core/Sidecar/BlockMedia.cs
@@ -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)
diff --git a/Aaru.Core/Sidecar/BlockTape.cs b/Aaru.Core/Sidecar/BlockTape.cs
index 5d7cbaf2c..d44ff1b65 100644
--- a/Aaru.Core/Sidecar/BlockTape.cs
+++ b/Aaru.Core/Sidecar/BlockTape.cs
@@ -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,
diff --git a/Aaru.Core/Sidecar/LinearMedia.cs b/Aaru.Core/Sidecar/LinearMedia.cs
index 39d4b92ef..0d4f22645 100644
--- a/Aaru.Core/Sidecar/LinearMedia.cs
+++ b/Aaru.Core/Sidecar/LinearMedia.cs
@@ -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
}
];
diff --git a/Aaru.Core/Sidecar/OpticalDisc.cs b/Aaru.Core/Sidecar/OpticalDisc.cs
index d3fb46ede..ff2926379 100644
--- a/Aaru.Core/Sidecar/OpticalDisc.cs
+++ b/Aaru.Core/Sidecar/OpticalDisc.cs
@@ -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
},
diff --git a/Aaru/Commands/Image/Convert.cs b/Aaru/Commands/Image/Convert.cs
index e32b022c6..c5f2ba26e 100644
--- a/Aaru/Commands/Image/Convert.cs
+++ b/Aaru/Commands/Image/Convert.cs
@@ -35,7 +35,6 @@ using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
-using System.Text;
using System.Text.Json;
using System.Threading;
using System.Xml.Serialization;
@@ -45,28 +44,22 @@ using Aaru.CommonTypes.Enums;
using Aaru.CommonTypes.Interfaces;
using Aaru.CommonTypes.Metadata;
using Aaru.Core;
-using Aaru.Core.Media;
-using Aaru.Decryption.DVD;
-using Aaru.Devices;
using Aaru.Localization;
using Aaru.Logging;
using Schemas;
using Spectre.Console;
using Spectre.Console.Cli;
+using Convert = Aaru.Core.Image.Convert;
using File = System.IO.File;
-using ImageInfo = Aaru.CommonTypes.Structs.ImageInfo;
using MediaType = Aaru.CommonTypes.MediaType;
-using Partition = Aaru.CommonTypes.Partition;
-using TapeFile = Aaru.CommonTypes.Structs.TapeFile;
-using TapePartition = Aaru.CommonTypes.Structs.TapePartition;
-using Track = Aaru.CommonTypes.Structs.Track;
-using Version = Aaru.CommonTypes.Interop.Version;
namespace Aaru.Commands.Image;
sealed class ConvertImageCommand : Command
{
- const string MODULE_NAME = "Convert-image command";
+ const string MODULE_NAME = "Convert-image command";
+ static ProgressTask _progressTask1;
+ static ProgressTask _progressTask2;
public override int Execute(CommandContext context, Settings settings, CancellationToken cancellationToken)
{
@@ -246,1123 +239,106 @@ sealed class ConvertImageCommand : Command
else
AaruLogging.WriteLine(UI.Output_image_format_0, outputFormat.Name);
- // Validate that output format supports the media type and tags
- int mediaCapabilityResult =
- ValidateMediaCapabilities(outputFormat as IWritableImage, inputFormat, mediaType, settings);
+ var converter = new Convert(inputFormat,
+ outputFormat as IWritableImage,
+ mediaType,
+ settings.Force,
+ settings.OutputPath,
+ parsedOptions,
+ nominalNegativeSectors,
+ nominalOverflowSectors,
+ settings.Comments,
+ settings.Creator,
+ settings.DriveFirmwareRevision,
+ settings.DriveManufacturer,
+ settings.DriveModel,
+ settings.DriveSerialNumber,
+ settings.LastMediaSequence,
+ settings.MediaBarcode,
+ settings.MediaManufacturer,
+ settings.MediaModel,
+ settings.MediaPartNumber,
+ settings.MediaSequence,
+ settings.MediaSerialNumber,
+ settings.MediaTitle,
+ settings.Decrypt,
+ (uint)settings.Count,
+ plugins,
+ fixSubchannelPosition,
+ fixSubchannel,
+ fixSubchannelCrc,
+ settings.GenerateSubchannels,
+ geometryValues,
+ resume,
+ sidecar);
- if(mediaCapabilityResult != (int)ErrorNumber.NoError) return mediaCapabilityResult;
+ ErrorNumber errno = ErrorNumber.NoError;
- // Validate sector tags compatibility between formats
+ AnsiConsole.Progress()
+ .AutoClear(true)
+ .HideCompleted(true)
+ .Columns(new ProgressBarColumn(), new PercentageColumn(), new TaskDescriptionColumn())
+ .Start(ctx =>
+ {
+ converter.UpdateStatus += static text => AaruLogging.WriteLine(text);
- int sectorTagValidationResult =
- ValidateSectorTags(outputFormat as IWritableImage, inputFormat, settings, out bool useLong);
+ converter.ErrorMessage += static text => AaruLogging.Error(text);
- if(sectorTagValidationResult != (int)ErrorNumber.NoError) return sectorTagValidationResult;
+ converter.StoppingErrorMessage += static text => AaruLogging.Error(text);
- // Check and setup tape image support if needed
- var inputTape = inputFormat as ITapeImage;
- var outputTape = outputFormat as IWritableTapeImage;
-
- int tapeValidationResult = ValidateTapeImage(inputTape, outputTape);
-
- if(tapeValidationResult != (int)ErrorNumber.NoError) return tapeValidationResult;
-
- var ret = false;
-
- int tapeSetupResult = SetupTapeImage(inputTape, outputTape, outputFormat as IWritableImage);
-
- if(tapeSetupResult != (int)ErrorNumber.NoError) return tapeSetupResult;
-
- // Validate optical media capabilities (sessions, hidden tracks, etc.)
- if((outputFormat as IWritableOpticalImage)?.OpticalCapabilities.HasFlag(OpticalImageCapabilities
- .CanStoreSessions) !=
- true &&
- (inputFormat as IOpticalMediaImage)?.Sessions?.Count > 1)
- {
- // TODO: Disabled until 6.0
- /*if(!_force)
- {*/
- AaruLogging.Error(Localization.Core.Output_format_does_not_support_sessions);
-
- return (int)ErrorNumber.UnsupportedMedia;
- /*}
-
- AaruLogging.ErrorWriteLine("Output format does not support sessions, this will end in a loss of data, continuing...");*/
- }
-
- // Check for hidden tracks support in optical media
- if((outputFormat as IWritableOpticalImage)?.OpticalCapabilities.HasFlag(OpticalImageCapabilities
- .CanStoreHiddenTracks) !=
- true &&
- (inputFormat as IOpticalMediaImage)?.Tracks?.Any(static t => t.Sequence == 0) == true)
- {
- // TODO: Disabled until 6.0
- /*if(!_force)
- {*/
- AaruLogging.Error(Localization.Core.Output_format_does_not_support_hidden_tracks);
-
- return (int)ErrorNumber.UnsupportedMedia;
- /*}
-
- AaruLogging.ErrorWriteLine("Output format does not support sessions, this will end in a loss of data, continuing...");*/
- }
-
- // Create the output image file with appropriate settings
- int createResult = CreateOutputImage(outputFormat as IWritableImage,
- settings.OutputPath,
- mediaType,
- parsedOptions,
- inputFormat,
- nominalNegativeSectors,
- nominalOverflowSectors);
-
- if(createResult != (int)ErrorNumber.NoError) return createResult;
-
- // Set image metadata in the output file
- int imageInfoResult = SetImageMetadata(inputFormat, outputFormat as IWritableImage, settings);
-
- if(imageInfoResult != (int)ErrorNumber.NoError) return imageInfoResult;
-
- // Prepare metadata and dump hardware information
- Metadata metadata = inputFormat.AaruMetadata;
- List dumpHardware = inputFormat.DumpHardware;
-
- // Convert media tags from input to output format
- int tagConversionResult = ConvertMediaTags(inputFormat, outputFormat as IWritableImage, settings);
-
- if(tagConversionResult != (int)ErrorNumber.NoError) return tagConversionResult;
-
- AaruLogging.WriteLine(UI._0_sectors_to_convert, inputFormat.Info.Sectors);
- ulong doneSectors = 0;
-
- // Handle optical media conversion (with tracks, subchannels, etc.)
- if(inputFormat is IOpticalMediaImage inputOptical &&
- outputFormat is IWritableOpticalImage outputOptical &&
- inputOptical.Tracks != null)
- {
- if(!outputOptical.SetTracks(inputOptical.Tracks))
- {
- AaruLogging.Error(UI.Error_0_sending_tracks_list_to_output_image, outputOptical.ErrorMessage);
-
- return (int)ErrorNumber.WriteError;
- }
-
- ErrorNumber errno = ErrorNumber.NoError;
-
- if(settings.Decrypt) AaruLogging.WriteLine("Decrypting encrypted sectors.");
-
- AnsiConsole.Progress()
- .AutoClear(true)
- .HideCompleted(true)
- .Columns(new TaskDescriptionColumn(), new ProgressBarColumn(), new PercentageColumn())
- .Start(ctx =>
+ converter.UpdateProgress += (text, current, maximum) =>
{
- ProgressTask discTask = ctx.AddTask(UI.Converting_disc);
- discTask.MaxValue = inputOptical.Tracks.Count;
- byte[] generatedTitleKeys = null;
+ _progressTask1 ??= ctx.AddTask("Progress");
+ _progressTask1.Description = text;
+ _progressTask1.Value = current;
+ _progressTask1.MaxValue = maximum;
+ };
- foreach(Track track in inputOptical.Tracks)
- {
- discTask.Description = string.Format(UI.Converting_sectors_in_track_0_of_1,
- discTask.Value + 1,
- discTask.MaxValue);
-
- doneSectors = 0;
- ulong trackSectors = track.EndSector - track.StartSector + 1;
-
- ProgressTask trackTask = ctx.AddTask(UI.Converting_track);
- trackTask.MaxValue = trackSectors;
-
- while(doneSectors < trackSectors)
- {
- byte[] sector;
-
- uint sectorsToDo;
-
- if(trackSectors - doneSectors >= (ulong)settings.Count)
- sectorsToDo = (uint)settings.Count;
- else
- sectorsToDo = (uint)(trackSectors - doneSectors);
-
- trackTask.Description = string.Format(UI.Converting_sectors_0_to_1_in_track_2,
- doneSectors + track.StartSector,
- doneSectors + sectorsToDo + track.StartSector,
- track.Sequence);
-
- var useNotLong = false;
- var result = false;
- SectorStatus sectorStatus = SectorStatus.NotDumped;
- var sectorStatusArray = new SectorStatus[1];
-
- 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(settings.Force)
- {
- AaruLogging.Error(UI.Error_0_reading_sector_1_continuing,
- errno,
- doneSectors + track.StartSector);
- }
- else
- {
- AaruLogging.Error(UI.Error_0_reading_sector_1_not_continuing,
- errno,
- doneSectors + track.StartSector);
-
- errno = ErrorNumber.WriteError;
-
- return;
- }
- }
-
- if(!result && sector.Length % 2352 != 0)
- {
- if(!settings.Force)
- {
- AaruLogging.Error(UI
- .Input_image_is_not_returning_raw_sectors_use_force_if_you_want_to_continue);
-
- errno = ErrorNumber.InOutError;
-
- return;
- }
-
- 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) && settings.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(settings.Force)
- {
- AaruLogging.Error(UI.Error_0_reading_sector_1_continuing,
- errno,
- doneSectors + track.StartSector);
- }
- else
- {
- AaruLogging.Error(UI.Error_0_reading_sector_1_not_continuing,
- errno,
- doneSectors + track.StartSector);
-
- errno = ErrorNumber.WriteError;
-
- return;
- }
- }
- }
-
- if(!result)
- {
- if(settings.Force)
- {
- AaruLogging.Error(UI.Error_0_writing_sector_1_continuing,
- outputOptical.ErrorMessage,
- doneSectors + track.StartSector);
- }
- else
- {
- AaruLogging.Error(UI.Error_0_writing_sector_1_not_continuing,
- outputOptical.ErrorMessage,
- doneSectors + track.StartSector);
-
- errno = ErrorNumber.WriteError;
-
- return;
- }
- }
-
- doneSectors += sectorsToDo;
- trackTask.Value += sectorsToDo;
- }
-
- trackTask.StopTask();
- discTask.Increment(1);
- }
- });
-
- if(errno != ErrorNumber.NoError) return (int)errno;
-
- Dictionary isrcs = new();
- Dictionary trackFlags = new();
- string mcn = null;
- HashSet subchannelExtents = [];
- Dictionary smallestPregapLbaPerTrack = new();
- var tracks = new Track[inputOptical.Tracks.Count];
-
- for(var i = 0; i < tracks.Length; i++)
- {
- tracks[i] = new Track
- {
- Indexes = new Dictionary(),
- 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 idx in inputOptical.Tracks[i].Indexes)
- tracks[i].Indexes[idx.Key] = idx.Value;
- }
-
- 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);
- }
- }
-
- 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];
- }
- }
-
- for(ulong s = 0; s < inputOptical.Info.Sectors; s++)
- {
- if(s > int.MaxValue) break;
-
- subchannelExtents.Add((int)s);
- }
-
- 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(settings.Force && !outputOptical.SupportedSectorTags.Contains(tag)) continue;
-
- errno = ErrorNumber.NoError;
-
- AnsiConsole.Progress()
- .AutoClear(true)
- .HideCompleted(true)
- .Columns(new TaskDescriptionColumn(), new ProgressBarColumn(), new PercentageColumn())
- .Start(ctx =>
- {
- ProgressTask discTask = ctx.AddTask(UI.Converting_disc);
- discTask.MaxValue = inputOptical.Tracks.Count;
-
- foreach(Track track in inputOptical.Tracks)
- {
- discTask.Description =
- string.Format(UI.Converting_tags_in_track_0_of_1,
- discTask.Value + 1,
- discTask.MaxValue);
-
- 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(settings.Force)
- {
- AaruLogging.Error(UI.Error_0_writing_tag_continuing,
- outputOptical.ErrorMessage);
-
- continue;
- }
-
- AaruLogging.Error(UI.Error_0_writing_tag_not_continuing,
- outputOptical.ErrorMessage);
-
- errno = ErrorNumber.WriteError;
-
- return;
- }
- }
-
- if(!result)
- {
- if(settings.Force)
- {
- AaruLogging.Error(UI.Error_0_writing_tag_continuing,
- outputOptical.ErrorMessage);
- }
- else
- {
- AaruLogging.Error(UI.Error_0_writing_tag_not_continuing,
- outputOptical.ErrorMessage);
-
- errno = ErrorNumber.WriteError;
-
- return;
- }
- }
-
- continue;
- }
-
- ProgressTask trackTask = ctx.AddTask(UI.Converting_track);
- trackTask.MaxValue = trackSectors;
-
- while(doneSectors < trackSectors)
- {
- uint sectorsToDo;
-
- if(trackSectors - doneSectors >= (ulong)settings.Count)
- sectorsToDo = (uint)settings.Count;
- else
- sectorsToDo = (uint)(trackSectors - doneSectors);
-
- trackTask.Description =
- string.Format(UI.Converting_tag_3_for_sectors_0_to_1_in_track_2,
- doneSectors + track.StartSector,
- doneSectors + sectorsToDo + track.StartSector,
- track.Sequence,
- tag);
-
- 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(settings.Force)
- {
- AaruLogging.Error(UI.Error_0_reading_tag_for_sector_1_continuing,
- errno,
- doneSectors + track.StartSector);
- }
- else
- {
- AaruLogging
- .Error(UI.Error_0_reading_tag_for_sector_1_not_continuing,
- errno,
- doneSectors + track.StartSector);
-
- return;
- }
- }
- }
- 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(settings.Force)
- {
- AaruLogging.Error(UI.Error_0_reading_tag_for_sector_1_continuing,
- errno,
- doneSectors + track.StartSector);
- }
- else
- {
- AaruLogging
- .Error(UI.Error_0_reading_tag_for_sector_1_not_continuing,
- errno,
- doneSectors + track.StartSector);
-
- return;
- }
- }
- }
-
- if(!result)
- {
- if(settings.Force)
- {
- AaruLogging.Error(UI.Error_0_writing_tag_for_sector_1_continuing,
- outputOptical.ErrorMessage,
- doneSectors + track.StartSector);
- }
- else
- {
- AaruLogging.Error(UI.Error_0_writing_tag_for_sector_1_not_continuing,
- outputOptical.ErrorMessage,
- doneSectors + track.StartSector);
-
- errno = ErrorNumber.WriteError;
-
- return;
- }
- }
-
- doneSectors += sectorsToDo;
- trackTask.Value += sectorsToDo;
- }
-
- trackTask.StopTask();
- discTask.Increment(1);
- }
- });
-
- if(errno != ErrorNumber.NoError && !settings.Force) return (int)errno;
- }
-
- foreach(KeyValuePair isrc in isrcs)
- {
- outputOptical.WriteSectorTag(Encoding.UTF8.GetBytes(isrc.Value),
- isrc.Key,
- false,
- SectorTagType.CdTrackIsrc);
- }
-
- if(trackFlags.Count > 0)
- {
- foreach((byte track, byte flags) in trackFlags)
- outputOptical.WriteSectorTag([flags], track, false, SectorTagType.CdTrackFlags);
- }
-
- if(mcn != null) outputOptical.WriteMediaTag(Encoding.UTF8.GetBytes(mcn), MediaTagType.CD_MCN);
-
- // TODO: Progress
- if(IsCompactDiscMedia(inputOptical.Info.MediaType) && settings.GenerateSubchannels)
- {
- Core.Spectre.ProgressSingleSpinner(ctx =>
- {
- ctx.AddTask(Localization.Core.Generating_subchannels).IsIndeterminate();
-
- CompactDisc.GenerateSubchannels(subchannelExtents,
- tracks,
- trackFlags,
- inputOptical.Info.Sectors,
- null,
- null,
- null,
- null,
- outputOptical);
- });
- }
- }
- else
- {
- var outputMedia = outputFormat as IWritableImage;
-
- 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)
- : (inputFormat.Info.Cylinders, inputFormat.Info.Heads, inputFormat.Info.SectorsPerTrack);
-
- AaruLogging.WriteLine(UI.Setting_geometry_to_0_cylinders_1_heads_and_2_sectors_per_track,
- chs.cylinders,
- chs.heads,
- chs.sectors);
-
- if(!outputMedia.SetGeometry(chs.cylinders, chs.heads, chs.sectors))
- {
- AaruLogging.Error(UI.Error_0_setting_geometry_image_may_be_incorrect_continuing,
- outputMedia.ErrorMessage);
- }
- }
-
- ErrorNumber errno = ErrorNumber.NoError;
-
- AnsiConsole.Progress()
- .AutoClear(true)
- .HideCompleted(true)
- .Columns(new TaskDescriptionColumn(), new ProgressBarColumn(), new PercentageColumn())
- .Start(ctx =>
+ converter.PulseProgress += text =>
{
- ProgressTask mediaTask = ctx.AddTask(UI.Converting_media);
- mediaTask.MaxValue = inputFormat.Info.Sectors;
-
- while(doneSectors < inputFormat.Info.Sectors)
+ if(_progressTask1 is null)
+ ctx.AddTask(text).IsIndeterminate();
+ else
{
- byte[] sector;
-
- uint sectorsToDo;
-
- if(inputTape?.IsTape == true)
- sectorsToDo = 1;
- else if(inputFormat.Info.Sectors - doneSectors >= (ulong)settings.Count)
- sectorsToDo = (uint)settings.Count;
- else
- sectorsToDo = (uint)(inputFormat.Info.Sectors - doneSectors);
-
- mediaTask.Description =
- string.Format(UI.Converting_sectors_0_to_1, doneSectors, doneSectors + sectorsToDo);
-
- bool result;
- SectorStatus sectorStatus = SectorStatus.NotDumped;
- var sectorStatusArray = new SectorStatus[1];
-
- if(useLong)
- {
- errno = sectorsToDo == 1
- ? inputFormat.ReadSectorLong(doneSectors,
- false,
- out sector,
- out sectorStatus)
- : inputFormat.ReadSectorsLong(doneSectors,
- false,
- sectorsToDo,
- out sector,
- out sectorStatusArray);
-
- if(errno == ErrorNumber.NoError)
- {
- result = sectorsToDo == 1
- ? outputMedia.WriteSectorLong(sector,
- doneSectors,
- false,
- sectorStatus)
- : outputMedia.WriteSectorsLong(sector,
- doneSectors,
- false,
- sectorsToDo,
- sectorStatusArray);
- }
- else
- {
- result = true;
-
- if(settings.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;
- }
- }
- }
- else
- {
- errno = sectorsToDo == 1
- ? inputFormat.ReadSector(doneSectors,
- false,
- out sector,
- out sectorStatus)
- : inputFormat.ReadSectors(doneSectors,
- false,
- sectorsToDo,
- out sector,
- out sectorStatusArray);
-
- if(errno == ErrorNumber.NoError)
- {
- result = sectorsToDo == 1
- ? outputMedia.WriteSector(sector, doneSectors, false, sectorStatus)
- : outputMedia.WriteSectors(sector,
- doneSectors,
- false,
- sectorsToDo,
- sectorStatusArray);
- }
- else
- {
- result = true;
-
- if(settings.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;
- }
- }
- }
-
- if(!result)
- {
- if(settings.Force)
- {
- AaruLogging.Error(UI.Error_0_writing_sector_1_continuing,
- outputMedia.ErrorMessage,
- doneSectors);
- }
- else
- {
- AaruLogging.Error(UI.Error_0_writing_sector_1_not_continuing,
- outputMedia.ErrorMessage,
- doneSectors);
-
- errno = ErrorNumber.WriteError;
-
- return;
- }
- }
-
- doneSectors += sectorsToDo;
- mediaTask.Value += sectorsToDo;
+ _progressTask1.Description = text;
+ _progressTask1.IsIndeterminate = true;
}
+ };
- mediaTask.StopTask();
+ converter.InitProgress += () => _progressTask1 = ctx.AddTask("Progress");
- foreach(SectorTagType tag in inputFormat.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:
- // This tags are inline in long sector
- continue;
- }
+ converter.EndProgress += static () =>
+ {
+ _progressTask1?.StopTask();
+ _progressTask1 = null;
+ };
- if(settings.Force && !outputMedia.SupportedSectorTags.Contains(tag)) continue;
+ converter.InitProgress2 += () => _progressTask2 = ctx.AddTask("Progress");
- doneSectors = 0;
+ converter.EndProgress2 += static () =>
+ {
+ _progressTask2?.StopTask();
+ _progressTask2 = null;
+ };
- ProgressTask tagsTask = ctx.AddTask(UI.Converting_tags);
- tagsTask.MaxValue = inputFormat.Info.Sectors;
+ converter.UpdateProgress2 += (text, current, maximum) =>
+ {
+ _progressTask2 ??= ctx.AddTask("Progress");
+ _progressTask2.Description = text;
+ _progressTask2.Value = current;
+ _progressTask2.MaxValue = maximum;
+ };
- while(doneSectors < inputFormat.Info.Sectors)
- {
- uint sectorsToDo;
+ Console.CancelKeyPress += (_, e) =>
+ {
+ e.Cancel = true;
+ converter.Abort();
+ };
- if(inputFormat.Info.Sectors - doneSectors >= (ulong)settings.Count)
- sectorsToDo = (uint)settings.Count;
- else
- sectorsToDo = (uint)(inputFormat.Info.Sectors - doneSectors);
+ errno = converter.Start();
+ });
- tagsTask.Description = string.Format(UI.Converting_tag_2_for_sectors_0_to_1,
- doneSectors,
- doneSectors + sectorsToDo,
- tag);
-
- bool result;
-
- errno = sectorsToDo == 1
- ? inputFormat.ReadSectorTag(doneSectors, false, tag, out byte[] sector)
- : inputFormat.ReadSectorsTag(doneSectors,
- false,
- sectorsToDo,
- tag,
- out sector);
-
- if(errno == ErrorNumber.NoError)
- {
- result = sectorsToDo == 1
- ? outputMedia.WriteSectorTag(sector, doneSectors, false, tag)
- : outputMedia.WriteSectorsTag(sector,
- doneSectors,
- false,
- sectorsToDo,
- tag);
- }
- else
- {
- result = true;
-
- if(settings.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;
- }
- }
-
- if(!result)
- {
- if(settings.Force)
- {
- AaruLogging.Error(UI.Error_0_writing_sector_1_continuing,
- outputMedia.ErrorMessage,
- doneSectors);
- }
- else
- {
- AaruLogging.Error(UI.Error_0_writing_sector_1_not_continuing,
- outputMedia.ErrorMessage,
- doneSectors);
-
- errno = ErrorNumber.WriteError;
-
- return;
- }
- }
-
- doneSectors += sectorsToDo;
- tagsTask.Value += sectorsToDo;
- }
-
- tagsTask.StopTask();
- }
-
- if(inputFormat is IFluxImage inputFlux && outputFormat is 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);
- }
- }
- }
- }
- }
-
- if(inputTape == null || outputTape == null || !inputTape.IsTape) return;
-
- ProgressTask filesTask = ctx.AddTask(UI.Converting_files);
- filesTask.MaxValue = inputTape.Files.Count;
-
- foreach(TapeFile tapeFile in inputTape.Files)
- {
- filesTask.Description =
- string.Format(UI.Converting_file_0_of_partition_1,
- tapeFile.File,
- tapeFile.Partition);
-
- outputTape.AddFile(tapeFile);
- filesTask.Increment(1);
- }
-
- filesTask.StopTask();
-
- ProgressTask partitionTask = ctx.AddTask(UI.Converting_files);
- partitionTask.MaxValue = inputTape.TapePartitions.Count;
-
- foreach(TapePartition tapePartition in inputTape.TapePartitions)
- {
- partitionTask.Description =
- string.Format(UI.Converting_tape_partition_0, tapePartition.Number);
-
- outputTape.AddPartition(tapePartition);
- }
-
- partitionTask.StopTask();
- });
-
- if(errno != ErrorNumber.NoError) return (int)errno;
- }
-
- if(nominalNegativeSectors > 0)
- {
- var outputMedia = outputFormat as IWritableImage;
-
- int negativeResult =
- ConvertNegativeSectors(inputFormat, outputMedia, nominalNegativeSectors, useLong, settings);
-
- if(negativeResult != (int)ErrorNumber.NoError) return negativeResult;
- }
-
-
- if(nominalOverflowSectors > 0)
- {
- var outputMedia = outputFormat as IWritableImage;
-
- int overflowResult =
- ConvertOverflowSectors(inputFormat, outputMedia, nominalOverflowSectors, useLong, settings);
-
- if(overflowResult != (int)ErrorNumber.NoError) return overflowResult;
- }
-
- if(resume != null || dumpHardware != null)
- {
- Core.Spectre.ProgressSingleSpinner(ctx =>
- {
- ctx.AddTask(UI.Writing_dump_hardware_list).IsIndeterminate();
-
- if(resume != null)
- ret = outputFormat.SetDumpHardware(resume.Tries);
- else if(dumpHardware != null) ret = outputFormat.SetDumpHardware(dumpHardware);
- });
-
- if(ret) AaruLogging.WriteLine(UI.Written_dump_hardware_list_to_output_image);
- }
-
- ret = false;
-
- if(sidecar != null || metadata != null)
- {
- Core.Spectre.ProgressSingleSpinner(ctx =>
- {
- ctx.AddTask(UI.Writing_metadata).IsIndeterminate();
-
- if(sidecar != null)
- ret = outputFormat.SetMetadata(sidecar);
- else if(metadata != null) ret = outputFormat.SetMetadata(metadata);
- });
-
- if(ret) AaruLogging.WriteLine(UI.Written_Aaru_Metadata_to_output_image);
- }
-
- var closed = false;
-
- Core.Spectre.ProgressSingleSpinner(ctx =>
- {
- ctx.AddTask(UI.Closing_output_image).IsIndeterminate();
- closed = outputFormat.Close();
- });
-
- if(!closed)
- {
- AaruLogging.Error(UI.Error_0_closing_output_image_Contents_are_not_correct, outputFormat.ErrorMessage);
-
- return (int)ErrorNumber.WriteError;
- }
-
- AaruLogging.WriteLine(UI.Conversion_done);
-
- return (int)ErrorNumber.NoError;
+ return (int)errno;
}
private (bool success, uint cylinders, uint heads, uint sectors)? ParseGeometry(string geometryString)
@@ -1584,357 +560,6 @@ sealed class ConvertImageCommand : Command
AaruLogging.Debug(MODULE_NAME, "{0} = {1}", parsedOption.Key, parsedOption.Value);
}
- private 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);
- }
- }
- }
- }
-
- private void GenerateDvdTitleKeys(IOpticalMediaImage inputOptical, PluginRegister plugins,
- ref byte[] generatedTitleKeys)
- {
- // 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
-
- List partitions = Core.Partitions.GetAll(inputOptical);
-
- partitions = partitions.FindAll(p =>
- {
- Core.Filesystems.Identify(inputOptical, out List idPlugins, p);
-
- return idPlugins.Contains("iso9660 filesystem");
- });
-
- if(!plugins.ReadOnlyFilesystems.TryGetValue("iso9660 filesystem", out IReadOnlyFilesystem rofs)) return;
-
- AaruLogging.Debug(MODULE_NAME, UI.Generating_decryption_keys);
-
- generatedTitleKeys = CSS.GenerateTitleKeys(inputOptical, partitions, inputOptical.Info.Sectors, rofs);
- }
-
- private 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;
-
- private int ConvertMediaTags(IMediaImage inputFormat, IWritableImage outputFormat, Settings settings)
- {
- // 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
-
- foreach(MediaTagType mediaTag in inputFormat.Info.ReadableMediaTags.Where(mediaTag => !settings.Force ||
- outputFormat.SupportedMediaTags.Contains(mediaTag)))
- {
- ErrorNumber errorNumber = ErrorNumber.NoError;
-
- AnsiConsole.Progress()
- .AutoClear(false)
- .HideCompleted(false)
- .Columns(new TaskDescriptionColumn(), new SpinnerColumn())
- .Start(ctx =>
- {
- ctx.AddTask(string.Format(UI.Converting_media_tag_0, Markup.Escape(mediaTag.ToString())));
- ErrorNumber errno = inputFormat.ReadMediaTag(mediaTag, out byte[] tag);
-
- if(errno != ErrorNumber.NoError)
- {
- if(settings.Force)
- AaruLogging.Error(UI.Error_0_reading_media_tag, errno);
- else
- {
- AaruLogging.Error(UI.Error_0_reading_media_tag_not_continuing, errno);
-
- errorNumber = errno;
- }
-
- return;
- }
-
- if(outputFormat?.WriteMediaTag(tag, mediaTag) == true) return;
-
- if(settings.Force)
- AaruLogging.Error(UI.Error_0_writing_media_tag, outputFormat?.ErrorMessage);
- else
- {
- AaruLogging.Error(UI.Error_0_writing_media_tag_not_continuing,
- outputFormat?.ErrorMessage);
-
- errorNumber = ErrorNumber.WriteError;
- }
- });
-
- if(errorNumber != ErrorNumber.NoError) return (int)errorNumber;
- }
-
- return (int)ErrorNumber.NoError;
- }
-
- private int SetImageMetadata(IMediaImage inputFormat, IWritableImage outputFormat, Settings settings)
- {
- // 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 ImageInfo
- {
- Application = "Aaru",
- ApplicationVersion = Version.GetInformationalVersion(),
- Comments = settings.Comments ?? inputFormat.Info.Comments,
- Creator = settings.Creator ?? inputFormat.Info.Creator,
- DriveFirmwareRevision = settings.DriveFirmwareRevision ?? inputFormat.Info.DriveFirmwareRevision,
- DriveManufacturer = settings.DriveManufacturer ?? inputFormat.Info.DriveManufacturer,
- DriveModel = settings.DriveModel ?? inputFormat.Info.DriveModel,
- DriveSerialNumber = settings.DriveSerialNumber ?? inputFormat.Info.DriveSerialNumber,
- LastMediaSequence =
- settings.LastMediaSequence != 0 ? settings.LastMediaSequence : inputFormat.Info.LastMediaSequence,
- MediaBarcode = settings.MediaBarcode ?? inputFormat.Info.MediaBarcode,
- MediaManufacturer = settings.MediaManufacturer ?? inputFormat.Info.MediaManufacturer,
- MediaModel = settings.MediaModel ?? inputFormat.Info.MediaModel,
- MediaPartNumber = settings.MediaPartNumber ?? inputFormat.Info.MediaPartNumber,
- MediaSequence = settings.MediaSequence != 0 ? settings.MediaSequence : inputFormat.Info.MediaSequence,
- MediaSerialNumber = settings.MediaSerialNumber ?? inputFormat.Info.MediaSerialNumber,
- MediaTitle = settings.MediaTitle ?? inputFormat.Info.MediaTitle
- };
-
- if(outputFormat.SetImageInfo(imageInfo)) return (int)ErrorNumber.NoError;
-
- if(!settings.Force)
- {
- AaruLogging.Error(UI.Error_0_setting_metadata_not_continuing, outputFormat.ErrorMessage);
-
- return (int)ErrorNumber.WriteError;
- }
-
- AaruLogging.Error(Localization.Core.Error_0_setting_metadata, outputFormat.ErrorMessage);
-
- return (int)ErrorNumber.NoError;
- }
-
- private int CreateOutputImage(IWritableImage outputFormat, string outputPath, MediaType mediaType,
- Dictionary parsedOptions, IMediaImage inputFormat,
- uint negativeSectors, uint overflowSectors)
- {
- // 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
- // Returns error code if creation fails
-
- var created = false;
-
- Core.Spectre.ProgressSingleSpinner(ctx =>
- {
- ctx.AddTask(UI.Invoke_Opening_image_file).IsIndeterminate();
-
- // TODO: Get the source image number of negative and overflow sectors to convert them too
- created = outputFormat.Create(outputPath,
- mediaType,
- parsedOptions,
- inputFormat.Info.Sectors,
- negativeSectors,
- overflowSectors,
- inputFormat.Info.SectorSize);
- });
-
- if(created) return (int)ErrorNumber.NoError;
-
- AaruLogging.Error(UI.Error_0_creating_output_image, outputFormat.ErrorMessage);
-
- return (int)ErrorNumber.CannotCreateFormat;
- }
-
- private int SetupTapeImage(ITapeImage inputTape, IWritableTapeImage outputTape, IWritableImage outputFormat)
- {
- // 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 (int)ErrorNumber.NoError;
-
- bool ret = outputTape.SetTape();
-
- // Cannot set image to tape mode
- if(ret) return (int)ErrorNumber.NoError;
-
- AaruLogging.Error(UI.Error_setting_output_image_in_tape_mode);
- AaruLogging.Error(outputFormat.ErrorMessage);
-
- return (int)ErrorNumber.WriteError;
- }
-
- private int 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 (int)ErrorNumber.NoError;
-
- AaruLogging.Error(UI.Input_format_contains_a_tape_image_and_is_not_supported_by_output_format);
-
- return (int)ErrorNumber.UnsupportedMedia;
- }
-
- private int ValidateSectorTags(IWritableImage outputFormat, IMediaImage inputFormat, Settings settings,
- 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 = inputFormat.Info.ReadableSectorTags.Count != 0;
-
- foreach(SectorTagType sectorTag in inputFormat.Info.ReadableSectorTags.Where(sectorTag =>
- !outputFormat.SupportedSectorTags.Contains(sectorTag)))
- {
- if(settings.Force)
- {
- if(sectorTag != SectorTagType.CdTrackFlags &&
- sectorTag != SectorTagType.CdTrackIsrc &&
- sectorTag != SectorTagType.CdSectorSubchannel)
- useLong = false;
-
- continue;
- }
-
- AaruLogging.Error(UI.Converting_image_will_lose_sector_tag_0, sectorTag);
-
- AaruLogging.Error(UI
- .If_you_dont_care_use_force_option_This_will_skip_all_sector_tags_converting_only_user_data);
-
- return (int)ErrorNumber.DataWillBeLost;
- }
-
- return (int)ErrorNumber.NoError;
- }
-
- private int ValidateMediaCapabilities(IWritableImage outputFormat, IMediaImage inputFormat, MediaType mediaType,
- Settings settings)
- {
- // 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)
- // Returns error if required features not supported and data would be lost
-
- if(!outputFormat.SupportedMediaTypes.Contains(mediaType))
- {
- AaruLogging.Error(UI.Output_format_does_not_support_media_type);
-
- return (int)ErrorNumber.UnsupportedMedia;
- }
-
- foreach(MediaTagType mediaTag in inputFormat.Info.ReadableMediaTags.Where(mediaTag =>
- !outputFormat.SupportedMediaTags.Contains(mediaTag) && !settings.Force))
- {
- AaruLogging.Error(UI.Converting_image_will_lose_media_tag_0, mediaTag);
- AaruLogging.Error(UI.If_you_dont_care_use_force_option);
-
- return (int)ErrorNumber.DataWillBeLost;
- }
-
- return (int)ErrorNumber.NoError;
- }
private IBaseWritableImage FindOutputFormat(PluginRegister plugins, string format, string outputPath)
{
@@ -1989,400 +614,6 @@ sealed class ConvertImageCommand : Command
return candidates[0];
}
- private int ConvertNegativeSectors(IMediaImage inputFormat, IWritableImage outputMedia, uint nominalNegativeSectors,
- bool useLong, Settings settings)
- {
- // 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
- // Returns error code if conversion fails in non-force mode
-
- ErrorNumber errno = ErrorNumber.NoError;
-
- AnsiConsole.Progress()
- .AutoClear(true)
- .HideCompleted(true)
- .Columns(new TaskDescriptionColumn(), new ProgressBarColumn(), new PercentageColumn())
- .Start(ctx =>
- {
- ProgressTask mediaTask = ctx.AddTask(UI.Converting_media);
- mediaTask.MaxValue = nominalNegativeSectors;
-
- // There's no -0
- for(uint i = 1; i <= nominalNegativeSectors; i++)
- {
- byte[] sector;
-
- mediaTask.Description =
- string.Format(UI.Converting_negative_sector_0_of_1, i, nominalNegativeSectors);
-
- bool result;
- SectorStatus sectorStatus;
-
- if(useLong)
- {
- errno = inputFormat.ReadSectorLong(i, true, out sector, out sectorStatus);
-
- if(errno == ErrorNumber.NoError)
- result = outputMedia.WriteSectorLong(sector, i, true, sectorStatus);
- else
- {
- result = true;
-
- if(settings.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;
- }
- }
- }
- else
- {
- errno = inputFormat.ReadSector(i, true, out sector, out sectorStatus);
-
- if(errno == ErrorNumber.NoError)
- result = outputMedia.WriteSector(sector, i, true, sectorStatus);
- else
- {
- result = true;
-
- if(settings.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;
- }
- }
- }
-
- if(!result)
- {
- if(settings.Force)
- {
- AaruLogging.Error(UI.Error_0_writing_negative_sector_1_continuing,
- outputMedia.ErrorMessage,
- i);
- }
- else
- {
- AaruLogging.Error(UI.Error_0_writing_negative_sector_1_not_continuing,
- outputMedia.ErrorMessage,
- i);
-
- errno = ErrorNumber.WriteError;
-
- return;
- }
- }
-
- mediaTask.Value++;
- }
-
- mediaTask.StopTask();
-
- foreach(SectorTagType tag in inputFormat.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(settings.Force && !outputMedia.SupportedSectorTags.Contains(tag)) continue;
-
- ProgressTask tagsTask = ctx.AddTask(UI.Converting_tags);
- tagsTask.MaxValue = nominalNegativeSectors;
-
- for(uint i = 1; i <= nominalNegativeSectors; i++)
- {
- tagsTask.Description = string.Format(UI.Converting_tag_1_for_negative_sector_0, i, tag);
-
- bool result;
-
- errno = inputFormat.ReadSectorTag(i, true, tag, out byte[] sector);
-
- if(errno == ErrorNumber.NoError)
- result = outputMedia.WriteSectorTag(sector, i, true, tag);
- else
- {
- result = true;
-
- if(settings.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;
- }
- }
-
- if(!result)
- {
- if(settings.Force)
- {
- AaruLogging.Error(UI.Error_0_writing_negative_sector_1_continuing,
- outputMedia.ErrorMessage,
- i);
- }
- else
- {
- AaruLogging.Error(UI.Error_0_writing_negative_sector_1_not_continuing,
- outputMedia.ErrorMessage,
- i);
-
- errno = ErrorNumber.WriteError;
-
- return;
- }
- }
-
- tagsTask.Value++;
- }
-
- tagsTask.StopTask();
- }
- });
-
- return (int)errno;
- }
-
- private int ConvertOverflowSectors(IMediaImage inputFormat, IWritableImage outputMedia, uint nominalOverflowSectors,
- bool useLong, Settings settings)
- {
- // 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;
-
- AnsiConsole.Progress()
- .AutoClear(true)
- .HideCompleted(true)
- .Columns(new TaskDescriptionColumn(), new ProgressBarColumn(), new PercentageColumn())
- .Start(ctx =>
- {
- ProgressTask mediaTask = ctx.AddTask(UI.Converting_media);
- mediaTask.MaxValue = nominalOverflowSectors;
-
- for(uint i = 0; i < nominalOverflowSectors; i++)
- {
- byte[] sector;
-
- mediaTask.Description =
- string.Format(UI.Converting_overflow_sector_0_of_1, i, nominalOverflowSectors);
-
- bool result;
- SectorStatus sectorStatus;
-
- if(useLong)
- {
- errno = inputFormat.ReadSectorLong(inputFormat.Info.Sectors + i,
- false,
- out sector,
- out sectorStatus);
-
- if(errno == ErrorNumber.NoError)
- {
- result = outputMedia.WriteSectorLong(sector,
- inputFormat.Info.Sectors + i,
- false,
- sectorStatus);
- }
- else
- {
- result = true;
-
- if(settings.Force)
- AaruLogging.Error(UI.Error_0_reading_overflow_sector_1_continuing, errno, i);
- else
- {
- AaruLogging.Error(UI.Error_0_reading_overflow_sector_1_not_continuing,
- errno,
- i);
-
- return;
- }
- }
- }
- else
- {
- errno = inputFormat.ReadSector(inputFormat.Info.Sectors + i,
- false,
- out sector,
- out sectorStatus);
-
- if(errno == ErrorNumber.NoError)
- {
- result = outputMedia.WriteSector(sector,
- inputFormat.Info.Sectors + i,
- false,
- sectorStatus);
- }
- else
- {
- result = true;
-
- if(settings.Force)
- AaruLogging.Error(UI.Error_0_reading_overflow_sector_1_continuing, errno, i);
- else
- {
- AaruLogging.Error(UI.Error_0_reading_overflow_sector_1_not_continuing,
- errno,
- i);
-
- return;
- }
- }
- }
-
- if(!result)
- {
- if(settings.Force)
- {
- AaruLogging.Error(UI.Error_0_writing_overflow_sector_1_continuing,
- outputMedia.ErrorMessage,
- i);
- }
- else
- {
- AaruLogging.Error(UI.Error_0_writing_overflow_sector_1_not_continuing,
- outputMedia.ErrorMessage,
- i);
-
- errno = ErrorNumber.WriteError;
-
- return;
- }
- }
-
- mediaTask.Value++;
- }
-
- mediaTask.StopTask();
-
- foreach(SectorTagType tag in inputFormat.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(settings.Force && !outputMedia.SupportedSectorTags.Contains(tag)) continue;
-
- ProgressTask tagsTask = ctx.AddTask(UI.Converting_tags);
- tagsTask.MaxValue = nominalOverflowSectors;
-
- for(uint i = 1; i <= nominalOverflowSectors; i++)
- {
- tagsTask.Description = string.Format(UI.Converting_tag_1_for_overflow_sector_0, i, tag);
-
- bool result;
-
- errno = inputFormat.ReadSectorTag(inputFormat.Info.Sectors + i,
- false,
- tag,
- out byte[] sector);
-
- if(errno == ErrorNumber.NoError)
- {
- result = outputMedia.WriteSectorTag(sector,
- inputFormat.Info.Sectors + i,
- false,
- tag);
- }
- else
- {
- result = true;
-
- if(settings.Force)
- {
- AaruLogging.Error(UI.Error_0_reading_overflow_sector_1_continuing,
- errno,
- inputFormat.Info.Sectors + i);
- }
- else
- {
- AaruLogging.Error(UI.Error_0_reading_overflow_sector_1_not_continuing,
- errno,
- inputFormat.Info.Sectors + i);
-
- return;
- }
- }
-
- if(!result)
- {
- if(settings.Force)
- {
- AaruLogging.Error(UI.Error_0_writing_overflow_sector_1_continuing,
- outputMedia.ErrorMessage,
- inputFormat.Info.Sectors + i);
- }
- else
- {
- AaruLogging.Error(UI.Error_0_writing_overflow_sector_1_not_continuing,
- outputMedia.ErrorMessage,
- inputFormat.Info.Sectors + i);
-
- errno = ErrorNumber.WriteError;
-
- return;
- }
- }
-
- tagsTask.Value++;
- }
-
- tagsTask.StopTask();
- }
- });
-
- return (int)errno;
- }
public class Settings : ImageFamily
{