From 63ff190275e6892ca43dc2c060d0ed18187173e7 Mon Sep 17 00:00:00 2001 From: Natalia Portillo Date: Tue, 25 Nov 2025 16:15:02 +0000 Subject: [PATCH] Rework image conversion command to separate into parts and move to Aaru.Core. --- Aaru.Core/Image/Convert/Capabilities.cs | 72 + Aaru.Core/Image/Convert/Convert.cs | 358 +++++ Aaru.Core/Image/Convert/Create.cs | 35 + Aaru.Core/Image/Convert/Edge.cs | 339 ++++ Aaru.Core/Image/Convert/Flux.cs | 52 + Aaru.Core/Image/Convert/Metadata.cs | 49 + Aaru.Core/Image/Convert/Optical.cs | 751 +++++++++ Aaru.Core/Image/Convert/Sectors.cs | 229 +++ Aaru.Core/Image/Convert/Tags.cs | 63 + Aaru.Core/Image/Convert/Tape.cs | 42 + Aaru.Core/Sidecar/AudioMedia.cs | 2 +- Aaru.Core/Sidecar/BlockMedia.cs | 8 +- Aaru.Core/Sidecar/BlockTape.cs | 6 +- Aaru.Core/Sidecar/LinearMedia.cs | 14 +- Aaru.Core/Sidecar/OpticalDisc.cs | 6 +- Aaru/Commands/Image/Convert.cs | 1941 +---------------------- 16 files changed, 2095 insertions(+), 1872 deletions(-) create mode 100644 Aaru.Core/Image/Convert/Capabilities.cs create mode 100644 Aaru.Core/Image/Convert/Convert.cs create mode 100644 Aaru.Core/Image/Convert/Create.cs create mode 100644 Aaru.Core/Image/Convert/Edge.cs create mode 100644 Aaru.Core/Image/Convert/Flux.cs create mode 100644 Aaru.Core/Image/Convert/Metadata.cs create mode 100644 Aaru.Core/Image/Convert/Optical.cs create mode 100644 Aaru.Core/Image/Convert/Sectors.cs create mode 100644 Aaru.Core/Image/Convert/Tags.cs create mode 100644 Aaru.Core/Image/Convert/Tape.cs 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 {