Rework image conversion command to separate into parts and move to Aaru.Core.

This commit is contained in:
2025-11-25 16:15:02 +00:00
parent 8efd9896c1
commit 63ff190275
16 changed files with 2095 additions and 1872 deletions

View File

@@ -0,0 +1,72 @@
using System;
using System.Linq;
using Aaru.CommonTypes.Enums;
using Aaru.Localization;
namespace Aaru.Core.Image;
public partial class Convert
{
/// <summary>
/// Validates media type and media tag support in output format
/// Checks if output format supports the media type being converted
/// Validates all readable media tags are supported by output (unless force mode enabled)
/// </summary>
/// <returns>Error if required features not supported and data would be lost</returns>
ErrorNumber ValidateMediaCapabilities()
{
if(!_outputImage.SupportedMediaTypes.Contains(_mediaType))
{
StoppingErrorMessage?.Invoke(UI.Output_format_does_not_support_media_type);
return ErrorNumber.UnsupportedMedia;
}
foreach(MediaTagType mediaTag in _inputImage.Info.ReadableMediaTags.Where(mediaTag =>
!_outputImage.SupportedMediaTags.Contains(mediaTag) && !_force))
{
StoppingErrorMessage?.Invoke(string.Format(UI.Converting_image_will_lose_media_tag_0 +
Environment.NewLine +
UI.If_you_dont_care_use_force_option,
mediaTag));
return ErrorNumber.DataWillBeLost;
}
return ErrorNumber.NoError;
}
ErrorNumber ValidateSectorTags(out bool useLong)
{
// Validates sector tag compatibility between formats
// Sets useLong flag based on sector tag support to determine sector size (512 vs 2352 bytes)
// Some tags like CD flags/ISRC don't require long sectors; subchannel data does
// In force mode, skips unsupported tags; otherwise reports error if data would be lost
useLong = _inputImage.Info.ReadableSectorTags.Count != 0;
foreach(SectorTagType sectorTag in _inputImage.Info.ReadableSectorTags.Where(sectorTag =>
!_outputImage.SupportedSectorTags.Contains(sectorTag)))
{
if(_force)
{
if(sectorTag != SectorTagType.CdTrackFlags &&
sectorTag != SectorTagType.CdTrackIsrc &&
sectorTag != SectorTagType.CdSectorSubchannel)
useLong = false;
continue;
}
StoppingErrorMessage.Invoke(string.Format(UI.Converting_image_will_lose_sector_tag_0 +
Environment.NewLine +
UI
.If_you_dont_care_use_force_option_This_will_skip_all_sector_tags_converting_only_user_data,
sectorTag));
return ErrorNumber.DataWillBeLost;
}
return ErrorNumber.NoError;
}
}

View File

@@ -0,0 +1,358 @@
using System.Collections.Generic;
using System.Linq;
using Aaru.CommonTypes;
using Aaru.CommonTypes.AaruMetadata;
using Aaru.CommonTypes.Enums;
using Aaru.CommonTypes.Interfaces;
using Aaru.CommonTypes.Metadata;
using Aaru.Localization;
using MediaType = Aaru.CommonTypes.MediaType;
using TapeFile = Aaru.CommonTypes.Structs.TapeFile;
using TapePartition = Aaru.CommonTypes.Structs.TapePartition;
namespace Aaru.Core.Image;
public partial class Convert
{
const string MODULE_NAME = "Image Conversion";
readonly string _comments;
readonly uint _count;
readonly string _creator;
readonly bool _decrypt;
readonly string _driveFirmwareRevision;
readonly string _driveManufacturer;
readonly string _driveModel;
readonly string _driveSerialNumber;
readonly bool _fixSubchannel;
readonly bool _fixSubchannelCrc;
readonly bool _fixSubchannelPosition;
readonly bool _force;
readonly bool _generateSubchannels;
readonly (uint cylinders, uint heads, uint sectors)? _geometryValues;
readonly IMediaImage _inputImage;
readonly int _lastMediaSequence;
readonly string _mediaBarcode;
readonly string _mediaManufacturer;
readonly string _mediaModel;
readonly string _mediaPartNumber;
readonly int _mediaSequence;
readonly string _mediaSerialNumber;
readonly string _mediaTitle;
readonly MediaType _mediaType;
readonly uint _negativeSectors;
readonly IWritableImage _outputImage;
readonly string _outputPath;
readonly uint _overflowSectors;
readonly Dictionary<string, string> _parsedOptions;
readonly PluginRegister _plugins;
readonly Resume _resume;
readonly Metadata _sidecar;
bool _aborted;
// TODO: Abort
public Convert(IMediaImage inputImage, IWritableImage outputImage, MediaType mediaType, bool force,
string outputPath, Dictionary<string, string> parsedOptions, uint negativeSectors,
uint overflowSectors, string comments, string creator, string driveFirmwareRevision,
string driveManufacturer, string driveModel, string driveSerialNumber, int lastMediaSequence,
string mediaBarcode, string mediaManufacturer, string mediaModel, string mediaPartNumber,
int mediaSequence, string mediaSerialNumber, string mediaTitle, bool decrypt, uint count,
PluginRegister plugins, bool fixSubchannelPosition, bool fixSubchannel, bool fixSubchannelCrc,
bool generateSubchannels, (uint cylinders, uint heads, uint sectors)? geometryValues, Resume resume,
Metadata sidecar)
{
_inputImage = inputImage;
_outputImage = outputImage;
_mediaType = mediaType;
_force = force;
_outputPath = outputPath;
_parsedOptions = parsedOptions;
_negativeSectors = negativeSectors;
_overflowSectors = overflowSectors;
_comments = comments;
_creator = creator;
_driveFirmwareRevision = driveFirmwareRevision;
_driveManufacturer = driveManufacturer;
_driveModel = driveModel;
_driveSerialNumber = driveSerialNumber;
_lastMediaSequence = lastMediaSequence;
_mediaBarcode = mediaBarcode;
_mediaManufacturer = mediaManufacturer;
_mediaModel = mediaModel;
_mediaPartNumber = mediaPartNumber;
_mediaSequence = mediaSequence;
_mediaSerialNumber = mediaSerialNumber;
_mediaTitle = mediaTitle;
_decrypt = decrypt;
_count = count;
_plugins = plugins;
_fixSubchannelPosition = fixSubchannelPosition;
_fixSubchannel = fixSubchannel;
_fixSubchannelCrc = fixSubchannelCrc;
_generateSubchannels = generateSubchannels;
_geometryValues = geometryValues;
_resume = resume;
_sidecar = sidecar;
}
public ErrorNumber Start()
{
// Validate that output format supports the media type and tags
ErrorNumber errno = ValidateMediaCapabilities();
if(errno != ErrorNumber.NoError) return errno;
// Validate sector tags compatibility between formats
errno = ValidateSectorTags(out bool useLong);
if(errno != ErrorNumber.NoError) return errno;
// Check and setup tape image support if needed
var inputTape = _inputImage as ITapeImage;
var outputTape = _outputImage as IWritableTapeImage;
errno = ValidateTapeImage(inputTape, outputTape);
if(errno != ErrorNumber.NoError) return errno;
var ret = false;
errno = SetupTapeImage(inputTape, outputTape);
if(errno != ErrorNumber.NoError) return errno;
// Validate optical media capabilities (sessions, hidden tracks, etc.)
if((_outputImage as IWritableOpticalImage)?.OpticalCapabilities.HasFlag(OpticalImageCapabilities
.CanStoreSessions) !=
true &&
(_inputImage as IOpticalMediaImage)?.Sessions?.Count > 1)
{
// TODO: Disabled until 6.0
/*if(!_force)
{*/
StoppingErrorMessage?.Invoke(Localization.Core.Output_format_does_not_support_sessions);
return ErrorNumber.UnsupportedMedia;
/*}
StoppingErrorMessage?.Invoke("Output format does not support sessions, this will end in a loss of data, continuing...");*/
}
// Check for hidden tracks support in optical media
if((_outputImage as IWritableOpticalImage)?.OpticalCapabilities.HasFlag(OpticalImageCapabilities
.CanStoreHiddenTracks) !=
true &&
(_inputImage as IOpticalMediaImage)?.Tracks?.Any(static t => t.Sequence == 0) == true)
{
// TODO: Disabled until 6.0
/*if(!_force)
{*/
StoppingErrorMessage?.Invoke(Localization.Core.Output_format_does_not_support_hidden_tracks);
return ErrorNumber.UnsupportedMedia;
/*}
StoppingErrorMessage?.Invoke("Output format does not support sessions, this will end in a loss of data, continuing...");*/
}
// Create the output image file with appropriate settings
errno = CreateOutputImage();
if(errno != ErrorNumber.NoError) return errno;
// Set image metadata in the output file
errno = SetImageMetadata();
if(errno != ErrorNumber.NoError) return errno;
// Prepare metadata and dump hardware information
Metadata metadata = _inputImage.AaruMetadata;
List<DumpHardware> dumpHardware = _inputImage.DumpHardware;
// Convert media tags from input to output format
errno = ConvertMediaTags();
if(errno != ErrorNumber.NoError && !_force) return errno;
UpdateStatus?.Invoke(string.Format(UI._0_sectors_to_convert, _inputImage.Info.Sectors));
// Perform the actual data conversion from input to output image
if(_inputImage is IOpticalMediaImage inputOptical &&
_outputImage is IWritableOpticalImage outputOptical &&
inputOptical.Tracks != null)
{
errno = ConvertOptical(inputOptical, outputOptical, useLong);
if(errno != ErrorNumber.NoError) return errno;
}
else
{
if(inputTape == null || outputTape == null || !inputTape.IsTape)
{
(uint cylinders, uint heads, uint sectors) chs =
_geometryValues != null
? (_geometryValues.Value.cylinders, _geometryValues.Value.heads, _geometryValues.Value.sectors)
: (_inputImage.Info.Cylinders, _inputImage.Info.Heads, _inputImage.Info.SectorsPerTrack);
UpdateStatus?.Invoke(string.Format(UI.Setting_geometry_to_0_cylinders_1_heads_and_2_sectors_per_track,
chs.cylinders,
chs.heads,
chs.sectors));
if(!_outputImage.SetGeometry(chs.cylinders, chs.heads, chs.sectors))
{
ErrorMessage?.Invoke(string.Format(UI.Error_0_setting_geometry_image_may_be_incorrect_continuing,
_outputImage.ErrorMessage));
}
}
errno = ConvertSectors(useLong, inputTape?.IsTape == true);
if(errno != ErrorNumber.NoError) return errno;
errno = ConvertSectorsTags(useLong);
if(errno != ErrorNumber.NoError) return errno;
if(_inputImage is IFluxImage inputFlux && _outputImage is IWritableFluxImage outputFlux)
{
errno = ConvertFlux(inputFlux, outputFlux);
if(errno != ErrorNumber.NoError) return errno;
}
if(inputTape != null && outputTape != null && inputTape.IsTape)
{
InitProgress?.Invoke();
var currentFile = 0;
foreach(TapeFile tapeFile in inputTape.Files)
{
UpdateProgress?.Invoke(string.Format(UI.Converting_file_0_of_partition_1,
tapeFile.File,
tapeFile.Partition),
currentFile + 1,
inputTape.Files.Count);
outputTape.AddFile(tapeFile);
currentFile++;
}
EndProgress?.Invoke();
InitProgress?.Invoke();
var currentPartition = 0;
foreach(TapePartition tapePartition in inputTape.TapePartitions)
{
UpdateProgress?.Invoke(string.Format(UI.Converting_tape_partition_0, tapePartition.Number),
currentPartition + 1,
inputTape.TapePartitions.Count);
outputTape.AddPartition(tapePartition);
currentPartition++;
}
EndProgress?.Invoke();
}
}
if(_negativeSectors > 0)
{
errno = ConvertNegativeSectors(useLong);
if(errno != ErrorNumber.NoError) return errno;
}
if(_overflowSectors > 0)
{
errno = ConvertOverflowSectors(useLong);
if(errno != ErrorNumber.NoError) return errno;
}
if(_resume != null || dumpHardware != null)
{
InitProgress?.Invoke();
PulseProgress?.Invoke(UI.Writing_dump_hardware_list);
if(_resume != null)
ret = _outputImage.SetDumpHardware(_resume.Tries);
else if(dumpHardware != null) ret = _outputImage.SetDumpHardware(dumpHardware);
if(ret) UpdateStatus?.Invoke(UI.Written_dump_hardware_list_to_output_image);
EndProgress?.Invoke();
}
ret = false;
if(_sidecar != null || metadata != null)
{
InitProgress?.Invoke();
PulseProgress?.Invoke(UI.Writing_metadata);
if(_sidecar != null)
ret = _outputImage.SetMetadata(_sidecar);
else if(metadata != null) ret = _outputImage.SetMetadata(metadata);
if(ret) UpdateStatus?.Invoke(UI.Written_Aaru_Metadata_to_output_image);
EndProgress?.Invoke();
}
var closed = false;
InitProgress?.Invoke();
PulseProgress?.Invoke(UI.Closing_output_image);
closed = _outputImage.Close();
EndProgress?.Invoke();
if(!closed)
{
StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_closing_output_image_Contents_are_not_correct,
_outputImage.ErrorMessage));
return ErrorNumber.WriteError;
}
UpdateStatus?.Invoke(UI.Conversion_done);
return ErrorNumber.NoError;
}
/// <summary>Event raised when the progress bar is no longer needed</summary>
public event EndProgressHandler EndProgress;
/// <summary>Event raised when a progress bar is needed</summary>
public event InitProgressHandler InitProgress;
/// <summary>Event raised to report status updates</summary>
public event UpdateStatusHandler UpdateStatus;
/// <summary>Event raised to report a non-fatal error</summary>
public event ErrorMessageHandler ErrorMessage;
/// <summary>Event raised to report a fatal error that stops the dumping operation and should call user's attention</summary>
public event ErrorMessageHandler StoppingErrorMessage;
/// <summary>Event raised to update the values of a determinate progress bar</summary>
public event UpdateProgressHandler UpdateProgress;
/// <summary>Event raised to update the status of an indeterminate progress bar</summary>
public event PulseProgressHandler PulseProgress;
/// <summary>Event raised when the progress bar is no longer needed</summary>
public event EndProgressHandler2 EndProgress2;
/// <summary>Event raised when a progress bar is needed</summary>
public event InitProgressHandler2 InitProgress2;
/// <summary>Event raised to update the values of a determinate progress bar</summary>
public event UpdateProgressHandler2 UpdateProgress2;
public void Abort()
{
_aborted = true;
}
}

View File

@@ -0,0 +1,35 @@
using Aaru.CommonTypes.Enums;
using Aaru.Localization;
namespace Aaru.Core.Image;
public partial class Convert
{
/// <summary>
/// Creates output image file with specified parameters
/// Calls the output format plugin's Create() method with sector count and format options
/// Shows progress indicator during file creation
/// </summary>
/// <returns>Error code if creation fails</returns>
private ErrorNumber CreateOutputImage()
{
InitProgress?.Invoke();
PulseProgress?.Invoke(UI.Invoke_Opening_image_file);
bool created = _outputImage.Create(_outputPath,
_mediaType,
_parsedOptions,
_inputImage.Info.Sectors,
_negativeSectors,
_overflowSectors,
_inputImage.Info.SectorSize);
EndProgress?.Invoke();
if(created) return ErrorNumber.NoError;
StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_creating_output_image, _outputImage.ErrorMessage));
return ErrorNumber.CannotCreateFormat;
}
}

View File

@@ -0,0 +1,339 @@
using System.Linq;
using Aaru.CommonTypes.Enums;
using Aaru.Localization;
using Aaru.Logging;
namespace Aaru.Core.Image;
public partial class Convert
{
/// <summary>
/// Converts negative sectors (pre-gap) from input to output image
/// Handles both long and short sector formats with progress indication
/// Also converts associated sector tags if present
/// </summary>
/// <returns>Error code if conversion fails in non-force mode</returns>
ErrorNumber ConvertNegativeSectors(bool useLong)
{
ErrorNumber errno = ErrorNumber.NoError;
InitProgress?.Invoke();
// There's no -0
for(uint i = 1; i <= _negativeSectors; i++)
{
byte[] sector;
UpdateProgress?.Invoke(string.Format(UI.Converting_negative_sector_0_of_1, i, _negativeSectors),
i,
_negativeSectors);
bool result;
SectorStatus sectorStatus;
if(useLong)
{
errno = _inputImage.ReadSectorLong(i, true, out sector, out sectorStatus);
if(errno == ErrorNumber.NoError)
result = _outputImage.WriteSectorLong(sector, i, true, sectorStatus);
else
{
result = true;
if(_force)
ErrorMessage?.Invoke(string.Format(UI.Error_0_reading_negative_sector_1_continuing, errno, i));
else
{
StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_reading_negative_sector_1_not_continuing,
errno,
i));
return errno;
}
}
}
else
{
errno = _inputImage.ReadSector(i, true, out sector, out sectorStatus);
if(errno == ErrorNumber.NoError)
result = _outputImage.WriteSector(sector, i, true, sectorStatus);
else
{
result = true;
if(_force)
ErrorMessage?.Invoke(string.Format(UI.Error_0_reading_negative_sector_1_continuing, errno, i));
else
{
StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_reading_negative_sector_1_not_continuing,
errno,
i));
return errno;
}
}
}
if(result) continue;
if(_force)
{
ErrorMessage?.Invoke(string.Format(UI.Error_0_writing_negative_sector_1_continuing,
_outputImage.ErrorMessage,
i));
}
else
{
StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_writing_negative_sector_1_not_continuing,
_outputImage.ErrorMessage,
i));
return ErrorNumber.WriteError;
}
}
EndProgress?.Invoke();
foreach(SectorTagType tag in _inputImage.Info.ReadableSectorTags.TakeWhile(_ => useLong))
{
switch(tag)
{
case SectorTagType.AppleSonyTag:
case SectorTagType.AppleProfileTag:
case SectorTagType.PriamDataTowerTag:
case SectorTagType.CdSectorSync:
case SectorTagType.CdSectorHeader:
case SectorTagType.CdSectorSubHeader:
case SectorTagType.CdSectorEdc:
case SectorTagType.CdSectorEccP:
case SectorTagType.CdSectorEccQ:
case SectorTagType.CdSectorEcc:
// These tags are inline in long sector
continue;
case SectorTagType.CdTrackFlags:
case SectorTagType.CdTrackIsrc:
case SectorTagType.CdTrackText:
// These tags are track tags
continue;
}
if(_force && !_outputImage.SupportedSectorTags.Contains(tag)) continue;
InitProgress?.Invoke();
for(uint i = 1; i <= _negativeSectors; i++)
{
UpdateProgress?.Invoke(string.Format(UI.Converting_tag_1_for_negative_sector_0, i, tag),
i,
_negativeSectors);
bool result;
errno = _inputImage.ReadSectorTag(i, true, tag, out byte[] sector);
if(errno == ErrorNumber.NoError)
result = _outputImage.WriteSectorTag(sector, i, true, tag);
else
{
result = true;
if(_force)
AaruLogging.Error(UI.Error_0_reading_negative_sector_1_continuing, errno, i);
else
{
AaruLogging.Error(UI.Error_0_reading_negative_sector_1_not_continuing, errno, i);
return errno;
}
}
if(result) continue;
if(_force)
AaruLogging.Error(UI.Error_0_writing_negative_sector_1_continuing, _outputImage.ErrorMessage, i);
else
{
AaruLogging.Error(UI.Error_0_writing_negative_sector_1_not_continuing,
_outputImage.ErrorMessage,
i);
return ErrorNumber.WriteError;
}
}
}
return errno;
}
ErrorNumber ConvertOverflowSectors(bool useLong)
{
// Converts overflow sectors (lead-out) from input to output image
// Handles both long and short sector formats with progress indication
// Also converts associated sector tags if present
// Returns error code if conversion fails in non-force mode
ErrorNumber errno = ErrorNumber.NoError;
InitProgress?.Invoke();
for(uint i = 0; i < _overflowSectors; i++)
{
byte[] sector;
UpdateProgress?.Invoke(string.Format(UI.Converting_overflow_sector_0_of_1, i, _overflowSectors),
i,
_overflowSectors);
bool result;
SectorStatus sectorStatus;
if(useLong)
{
errno = _inputImage.ReadSectorLong(_inputImage.Info.Sectors + i, false, out sector, out sectorStatus);
if(errno == ErrorNumber.NoError)
result = _outputImage.WriteSectorLong(sector, _inputImage.Info.Sectors + i, false, sectorStatus);
else
{
result = true;
if(_force)
ErrorMessage?.Invoke(string.Format(UI.Error_0_reading_overflow_sector_1_continuing, errno, i));
else
{
StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_reading_overflow_sector_1_not_continuing,
errno,
i));
return errno;
}
}
}
else
{
errno = _inputImage.ReadSector(_inputImage.Info.Sectors + i, false, out sector, out sectorStatus);
if(errno == ErrorNumber.NoError)
result = _outputImage.WriteSector(sector, _inputImage.Info.Sectors + i, false, sectorStatus);
else
{
result = true;
if(_force)
ErrorMessage?.Invoke(string.Format(UI.Error_0_reading_overflow_sector_1_continuing, errno, i));
else
{
StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_reading_overflow_sector_1_not_continuing,
errno,
i));
return errno;
}
}
}
if(result) continue;
if(_force)
{
ErrorMessage?.Invoke(string.Format(UI.Error_0_writing_overflow_sector_1_continuing,
_outputImage.ErrorMessage,
i));
}
else
{
StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_writing_overflow_sector_1_not_continuing,
_outputImage.ErrorMessage,
i));
return ErrorNumber.WriteError;
}
}
EndProgress?.Invoke();
foreach(SectorTagType tag in _inputImage.Info.ReadableSectorTags.TakeWhile(_ => useLong))
{
switch(tag)
{
case SectorTagType.AppleSonyTag:
case SectorTagType.AppleProfileTag:
case SectorTagType.PriamDataTowerTag:
case SectorTagType.CdSectorSync:
case SectorTagType.CdSectorHeader:
case SectorTagType.CdSectorSubHeader:
case SectorTagType.CdSectorEdc:
case SectorTagType.CdSectorEccP:
case SectorTagType.CdSectorEccQ:
case SectorTagType.CdSectorEcc:
// These tags are inline in long sector
continue;
case SectorTagType.CdTrackFlags:
case SectorTagType.CdTrackIsrc:
case SectorTagType.CdTrackText:
// These tags are track tags
continue;
}
if(_force && !_outputImage.SupportedSectorTags.Contains(tag)) continue;
InitProgress?.Invoke();
for(uint i = 1; i <= _overflowSectors; i++)
{
UpdateProgress?.Invoke(string.Format(UI.Converting_tag_1_for_overflow_sector_0, i, tag),
i,
_overflowSectors);
bool result;
errno = _inputImage.ReadSectorTag(_inputImage.Info.Sectors + i, false, tag, out byte[] sector);
if(errno == ErrorNumber.NoError)
result = _outputImage.WriteSectorTag(sector, _inputImage.Info.Sectors + i, false, tag);
else
{
result = true;
if(_force)
{
ErrorMessage?.Invoke(string.Format(UI.Error_0_reading_overflow_sector_1_continuing,
errno,
_inputImage.Info.Sectors + i));
}
else
{
StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_reading_overflow_sector_1_not_continuing,
errno,
_inputImage.Info.Sectors + i));
return errno;
}
}
if(result) continue;
if(_force)
{
ErrorMessage?.Invoke(string.Format(UI.Error_0_writing_overflow_sector_1_continuing,
_outputImage.ErrorMessage,
_inputImage.Info.Sectors + i));
}
else
{
StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_writing_overflow_sector_1_not_continuing,
_outputImage.ErrorMessage,
_inputImage.Info.Sectors + i));
return ErrorNumber.WriteError;
}
}
EndProgress?.Invoke();
}
return errno;
}
}

View File

@@ -0,0 +1,52 @@
using Aaru.CommonTypes.Enums;
using Aaru.CommonTypes.Interfaces;
namespace Aaru.Core.Image;
public partial class Convert
{
// TODO: Should we return error any time?
// TODO: Add progress reporting
ErrorNumber ConvertFlux(IFluxImage inputFlux, IWritableFluxImage outputFlux)
{
for(ushort track = 0; track < inputFlux.Info.Cylinders; track++)
{
for(uint head = 0; head < inputFlux.Info.Heads; head++)
{
ErrorNumber error = inputFlux.SubTrackLength(head, track, out byte subTrackLen);
if(error != ErrorNumber.NoError) continue;
for(byte subTrackIndex = 0; subTrackIndex < subTrackLen; subTrackIndex++)
{
error = inputFlux.CapturesLength(head, track, subTrackIndex, out uint capturesLen);
if(error != ErrorNumber.NoError) continue;
for(uint captureIndex = 0; captureIndex < capturesLen; captureIndex++)
{
inputFlux.ReadFluxCapture(head,
track,
subTrackIndex,
captureIndex,
out ulong indexResolution,
out ulong dataResolution,
out byte[] indexBuffer,
out byte[] dataBuffer);
outputFlux.WriteFluxCapture(indexResolution,
dataResolution,
indexBuffer,
dataBuffer,
head,
track,
subTrackIndex,
captureIndex);
}
}
}
}
return ErrorNumber.NoError;
}
}

View File

@@ -0,0 +1,49 @@
using Aaru.CommonTypes.Enums;
using Aaru.CommonTypes.Interop;
using Aaru.Localization;
namespace Aaru.Core.Image;
public partial class Convert
{
ErrorNumber SetImageMetadata()
{
// Builds and applies complete ImageInfo metadata to output image
// Copies input metadata and applies command-line overrides (title, comments, creator, drive info, etc.)
// Sets Aaru application version and applies all metadata fields to output format
var imageInfo = new CommonTypes.Structs.ImageInfo
{
Application = "Aaru",
ApplicationVersion = Version.GetInformationalVersion(),
Comments = _comments ?? _inputImage.Info.Comments,
Creator = _creator ?? _inputImage.Info.Creator,
DriveFirmwareRevision = _driveFirmwareRevision ?? _inputImage.Info.DriveFirmwareRevision,
DriveManufacturer = _driveManufacturer ?? _inputImage.Info.DriveManufacturer,
DriveModel = _driveModel ?? _inputImage.Info.DriveModel,
DriveSerialNumber = _driveSerialNumber ?? _inputImage.Info.DriveSerialNumber,
LastMediaSequence = _lastMediaSequence != 0 ? _lastMediaSequence : _inputImage.Info.LastMediaSequence,
MediaBarcode = _mediaBarcode ?? _inputImage.Info.MediaBarcode,
MediaManufacturer = _mediaManufacturer ?? _inputImage.Info.MediaManufacturer,
MediaModel = _mediaModel ?? _inputImage.Info.MediaModel,
MediaPartNumber = _mediaPartNumber ?? _inputImage.Info.MediaPartNumber,
MediaSequence = _mediaSequence != 0 ? _mediaSequence : _inputImage.Info.MediaSequence,
MediaSerialNumber = _mediaSerialNumber ?? _inputImage.Info.MediaSerialNumber,
MediaTitle = _mediaTitle ?? _inputImage.Info.MediaTitle
};
if(_outputImage.SetImageInfo(imageInfo)) return ErrorNumber.NoError;
if(!_force)
{
StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_setting_metadata_not_continuing,
_outputImage.ErrorMessage));
return ErrorNumber.WriteError;
}
ErrorMessage?.Invoke(string.Format(Localization.Core.Error_0_setting_metadata, _outputImage.ErrorMessage));
return ErrorNumber.NoError;
}
}

View File

@@ -0,0 +1,751 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Aaru.CommonTypes;
using Aaru.CommonTypes.Enums;
using Aaru.CommonTypes.Interfaces;
using Aaru.CommonTypes.Structs;
using Aaru.Core.Media;
using Aaru.Decryption.DVD;
using Aaru.Devices;
using Aaru.Localization;
using Aaru.Logging;
namespace Aaru.Core.Image;
public partial class Convert
{
ErrorNumber ConvertOptical(IOpticalMediaImage inputOptical, IWritableOpticalImage outputOptical, bool useLong)
{
if(!outputOptical.SetTracks(inputOptical.Tracks))
{
StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_sending_tracks_list_to_output_image,
outputOptical.ErrorMessage));
return ErrorNumber.WriteError;
}
if(_decrypt) UpdateStatus?.Invoke("Decrypting encrypted sectors.");
// Convert all sectors track by track
ErrorNumber errno = ConvertOpticalSectors(inputOptical, outputOptical, useLong);
if(errno != ErrorNumber.NoError) return errno;
Dictionary<byte, string> isrcs = new();
Dictionary<byte, byte> trackFlags = new();
string mcn = null;
HashSet<int> subchannelExtents = [];
Dictionary<byte, int> smallestPregapLbaPerTrack = new();
var tracks = new Track[inputOptical.Tracks.Count];
for(var i = 0; i < tracks.Length; i++)
{
tracks[i] = new Track
{
Indexes = new Dictionary<ushort, int>(),
Description = inputOptical.Tracks[i].Description,
EndSector = inputOptical.Tracks[i].EndSector,
StartSector = inputOptical.Tracks[i].StartSector,
Pregap = inputOptical.Tracks[i].Pregap,
Sequence = inputOptical.Tracks[i].Sequence,
Session = inputOptical.Tracks[i].Session,
BytesPerSector = inputOptical.Tracks[i].BytesPerSector,
RawBytesPerSector = inputOptical.Tracks[i].RawBytesPerSector,
Type = inputOptical.Tracks[i].Type,
SubchannelType = inputOptical.Tracks[i].SubchannelType
};
foreach(KeyValuePair<ushort, int> idx in inputOptical.Tracks[i].Indexes)
tracks[i].Indexes[idx.Key] = idx.Value;
}
// Gets tracks ISRCs
foreach(SectorTagType tag in inputOptical.Info.ReadableSectorTags
.Where(static t => t == SectorTagType.CdTrackIsrc)
.Order())
{
foreach(Track track in tracks)
{
errno = inputOptical.ReadSectorTag(track.Sequence, false, tag, out byte[] isrc);
if(errno != ErrorNumber.NoError) continue;
isrcs[(byte)track.Sequence] = Encoding.UTF8.GetString(isrc);
}
}
// Gets tracks flags
foreach(SectorTagType tag in inputOptical.Info.ReadableSectorTags
.Where(static t => t == SectorTagType.CdTrackFlags)
.Order())
{
foreach(Track track in tracks)
{
errno = inputOptical.ReadSectorTag(track.Sequence, false, tag, out byte[] flags);
if(errno != ErrorNumber.NoError) continue;
trackFlags[(byte)track.Sequence] = flags[0];
}
}
// Gets subchannel extents
for(ulong s = 0; s < inputOptical.Info.Sectors; s++)
{
if(s > int.MaxValue) break;
subchannelExtents.Add((int)s);
}
errno = ConvertOpticalSectorsTags(inputOptical,
outputOptical,
useLong,
isrcs,
ref mcn,
tracks,
subchannelExtents,
smallestPregapLbaPerTrack);
if(errno != ErrorNumber.NoError) return errno;
// Write ISRCs
foreach(KeyValuePair<byte, string> isrc in isrcs)
{
outputOptical.WriteSectorTag(Encoding.UTF8.GetBytes(isrc.Value),
isrc.Key,
false,
SectorTagType.CdTrackIsrc);
}
// Write track flags
if(trackFlags.Count > 0)
{
foreach((byte track, byte flags) in trackFlags)
outputOptical.WriteSectorTag([flags], track, false, SectorTagType.CdTrackFlags);
}
// Write MCN
if(mcn != null) outputOptical.WriteMediaTag(Encoding.UTF8.GetBytes(mcn), MediaTagType.CD_MCN);
if(!IsCompactDiscMedia(inputOptical.Info.MediaType) || !_generateSubchannels) return ErrorNumber.NoError;
// Generate subchannel data
CompactDisc.GenerateSubchannels(subchannelExtents,
tracks,
trackFlags,
inputOptical.Info.Sectors,
null,
InitProgress,
UpdateProgress,
EndProgress,
outputOptical);
return ErrorNumber.NoError;
}
ErrorNumber ConvertOpticalSectors(IOpticalMediaImage inputOptical, IWritableOpticalImage outputOptical,
bool useLong)
{
InitProgress?.Invoke();
InitProgress2?.Invoke();
byte[] generatedTitleKeys = null;
var currentTrack = 0;
foreach(Track track in inputOptical.Tracks)
{
UpdateProgress?.Invoke(string.Format(UI.Converting_sectors_in_track_0_of_1,
currentTrack + 1,
inputOptical.Tracks.Count),
currentTrack,
inputOptical.Tracks.Count);
ulong doneSectors = 0;
ulong trackSectors = track.EndSector - track.StartSector + 1;
while(doneSectors < trackSectors)
{
byte[] sector;
uint sectorsToDo;
if(trackSectors - doneSectors >= _count)
sectorsToDo = _count;
else
sectorsToDo = (uint)(trackSectors - doneSectors);
UpdateProgress2?.Invoke(string.Format(UI.Converting_sectors_0_to_1_in_track_2,
doneSectors + track.StartSector,
doneSectors + sectorsToDo + track.StartSector,
track.Sequence),
(long)doneSectors,
(long)trackSectors);
var useNotLong = false;
var result = false;
SectorStatus sectorStatus = SectorStatus.NotDumped;
var sectorStatusArray = new SectorStatus[1];
ErrorNumber errno;
if(useLong)
{
errno = sectorsToDo == 1
? inputOptical.ReadSectorLong(doneSectors + track.StartSector,
false,
out sector,
out sectorStatus)
: inputOptical.ReadSectorsLong(doneSectors + track.StartSector,
false,
sectorsToDo,
out sector,
out sectorStatusArray);
if(errno == ErrorNumber.NoError)
{
result = sectorsToDo == 1
? outputOptical.WriteSectorLong(sector,
doneSectors + track.StartSector,
false,
sectorStatus)
: outputOptical.WriteSectorsLong(sector,
doneSectors + track.StartSector,
false,
sectorsToDo,
sectorStatusArray);
}
else
{
result = true;
if(_force)
{
ErrorMessage?.Invoke(string.Format(UI.Error_0_reading_sector_1_continuing,
errno,
doneSectors + track.StartSector));
}
else
{
StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_reading_sector_1_not_continuing,
errno,
doneSectors + track.StartSector));
return ErrorNumber.WriteError;
}
}
if(!result && sector.Length % 2352 != 0)
{
if(!_force)
{
StoppingErrorMessage
?.Invoke(UI.Input_image_is_not_returning_raw_sectors_use_force_if_you_want_to_continue);
return ErrorNumber.InOutError;
}
useNotLong = true;
}
}
if(!useLong || useNotLong)
{
errno = sectorsToDo == 1
? inputOptical.ReadSector(doneSectors + track.StartSector,
false,
out sector,
out sectorStatus)
: inputOptical.ReadSectors(doneSectors + track.StartSector,
false,
sectorsToDo,
out sector,
out sectorStatusArray);
// TODO: Move to generic place when anything but CSS DVDs can be decrypted
if(IsDvdMedia(inputOptical.Info.MediaType) && _decrypt)
{
DecryptDvdSector(ref sector,
inputOptical,
doneSectors + track.StartSector,
sectorsToDo,
_plugins,
ref generatedTitleKeys);
}
if(errno == ErrorNumber.NoError)
{
result = sectorsToDo == 1
? outputOptical.WriteSector(sector,
doneSectors + track.StartSector,
false,
sectorStatus)
: outputOptical.WriteSectors(sector,
doneSectors + track.StartSector,
false,
sectorsToDo,
sectorStatusArray);
}
else
{
result = true;
if(_force)
{
ErrorMessage?.Invoke(string.Format(UI.Error_0_reading_sector_1_continuing,
errno,
doneSectors + track.StartSector));
}
else
{
StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_reading_sector_1_not_continuing,
errno,
doneSectors + track.StartSector));
return ErrorNumber.WriteError;
}
}
}
if(!result)
{
if(_force)
{
ErrorMessage?.Invoke(string.Format(UI.Error_0_writing_sector_1_continuing,
outputOptical.ErrorMessage,
doneSectors + track.StartSector));
}
else
{
StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_writing_sector_1_not_continuing,
outputOptical.ErrorMessage,
doneSectors + track.StartSector));
return ErrorNumber.WriteError;
}
}
doneSectors += sectorsToDo;
}
currentTrack++;
}
EndProgress2?.Invoke();
EndProgress?.Invoke();
return ErrorNumber.NoError;
}
ErrorNumber ConvertOpticalSectorsTags(IOpticalMediaImage inputOptical, IWritableOpticalImage outputOptical,
bool useLong, Dictionary<byte, string> isrcs, ref string mcn, Track[] tracks,
HashSet<int> subchannelExtents,
Dictionary<byte, int> smallestPregapLbaPerTrack)
{
foreach(SectorTagType tag in inputOptical.Info.ReadableSectorTags.Order().TakeWhile(_ => useLong))
{
switch(tag)
{
case SectorTagType.AppleSonyTag:
case SectorTagType.AppleProfileTag:
case SectorTagType.PriamDataTowerTag:
case SectorTagType.CdSectorSync:
case SectorTagType.CdSectorHeader:
case SectorTagType.CdSectorSubHeader:
case SectorTagType.CdSectorEdc:
case SectorTagType.CdSectorEccP:
case SectorTagType.CdSectorEccQ:
case SectorTagType.CdSectorEcc:
case SectorTagType.DvdSectorCmi:
case SectorTagType.DvdSectorTitleKey:
case SectorTagType.DvdSectorEdc:
case SectorTagType.DvdSectorIed:
case SectorTagType.DvdSectorInformation:
case SectorTagType.DvdSectorNumber:
// This tags are inline in long sector
continue;
}
if(_force && !outputOptical.SupportedSectorTags.Contains(tag)) continue;
ErrorNumber errno = ErrorNumber.NoError;
InitProgress?.Invoke();
InitProgress2?.Invoke();
var currentTrack = 0;
foreach(Track track in inputOptical.Tracks)
{
UpdateProgress?.Invoke(string.Format(UI.Converting_tags_in_track_0_of_1,
currentTrack + 1,
inputOptical.Tracks.Count),
currentTrack,
inputOptical.Tracks.Count);
ulong doneSectors = 0;
ulong trackSectors = track.EndSector - track.StartSector + 1;
byte[] sector;
bool result;
switch(tag)
{
case SectorTagType.CdTrackFlags:
case SectorTagType.CdTrackIsrc:
errno = inputOptical.ReadSectorTag(track.Sequence, false, tag, out sector);
switch(errno)
{
case ErrorNumber.NoData:
errno = ErrorNumber.NoError;
continue;
case ErrorNumber.NoError:
result = outputOptical.WriteSectorTag(sector, track.Sequence, false, tag);
break;
default:
{
if(_force)
{
ErrorMessage?.Invoke(string.Format(UI.Error_0_writing_tag_continuing,
outputOptical.ErrorMessage));
continue;
}
StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_writing_tag_not_continuing,
outputOptical.ErrorMessage));
return ErrorNumber.WriteError;
}
}
if(!result)
{
if(_force)
{
ErrorMessage?.Invoke(string.Format(UI.Error_0_writing_tag_continuing,
outputOptical.ErrorMessage));
}
else
{
StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_writing_tag_not_continuing,
outputOptical.ErrorMessage));
return ErrorNumber.WriteError;
}
}
continue;
}
while(doneSectors < trackSectors)
{
uint sectorsToDo;
if(trackSectors - doneSectors >= _count)
sectorsToDo = _count;
else
sectorsToDo = (uint)(trackSectors - doneSectors);
UpdateProgress2?.Invoke(string.Format(UI.Converting_tag_3_for_sectors_0_to_1_in_track_2,
doneSectors + track.StartSector,
doneSectors + sectorsToDo + track.StartSector,
track.Sequence,
tag),
(long)(doneSectors + track.StartSector),
(long)(doneSectors + sectorsToDo + track.StartSector));
if(sectorsToDo == 1)
{
errno = inputOptical.ReadSectorTag(doneSectors + track.StartSector, false, tag, out sector);
if(errno == ErrorNumber.NoError)
{
if(tag == SectorTagType.CdSectorSubchannel)
{
bool indexesChanged = CompactDisc.WriteSubchannelToImage(MmcSubchannel.Raw,
MmcSubchannel.Raw,
sector,
doneSectors + track.StartSector,
1,
null,
isrcs,
(byte)track.Sequence,
ref mcn,
tracks,
subchannelExtents,
_fixSubchannelPosition,
outputOptical,
_fixSubchannel,
_fixSubchannelCrc,
null,
smallestPregapLbaPerTrack,
false,
out _);
if(indexesChanged) outputOptical.SetTracks(tracks.ToList());
result = true;
}
else
{
result = outputOptical.WriteSectorTag(sector,
doneSectors + track.StartSector,
false,
tag);
}
}
else
{
result = true;
if(_force)
{
ErrorMessage?.Invoke(string.Format(UI.Error_0_reading_tag_for_sector_1_continuing,
errno,
doneSectors + track.StartSector));
}
else
{
StoppingErrorMessage
?.Invoke(string.Format(UI.Error_0_reading_tag_for_sector_1_not_continuing,
errno,
doneSectors + track.StartSector));
return errno;
}
}
}
else
{
errno = inputOptical.ReadSectorsTag(doneSectors + track.StartSector,
false,
sectorsToDo,
tag,
out sector);
if(errno == ErrorNumber.NoError)
{
if(tag == SectorTagType.CdSectorSubchannel)
{
bool indexesChanged = CompactDisc.WriteSubchannelToImage(MmcSubchannel.Raw,
MmcSubchannel.Raw,
sector,
doneSectors + track.StartSector,
sectorsToDo,
null,
isrcs,
(byte)track.Sequence,
ref mcn,
tracks,
subchannelExtents,
_fixSubchannelPosition,
outputOptical,
_fixSubchannel,
_fixSubchannelCrc,
null,
smallestPregapLbaPerTrack,
false,
out _);
if(indexesChanged) outputOptical.SetTracks(tracks.ToList());
result = true;
}
else
{
result = outputOptical.WriteSectorsTag(sector,
doneSectors + track.StartSector,
false,
sectorsToDo,
tag);
}
}
else
{
result = true;
if(_force)
{
ErrorMessage?.Invoke(string.Format(UI.Error_0_reading_tag_for_sector_1_continuing,
errno,
doneSectors + track.StartSector));
}
else
{
StoppingErrorMessage
?.Invoke(string.Format(UI.Error_0_reading_tag_for_sector_1_not_continuing,
errno,
doneSectors + track.StartSector));
return errno;
}
}
}
if(!result)
{
if(_force)
{
ErrorMessage?.Invoke(string.Format(UI.Error_0_writing_tag_for_sector_1_continuing,
outputOptical.ErrorMessage,
doneSectors + track.StartSector));
}
else
{
StoppingErrorMessage
?.Invoke(string.Format(UI.Error_0_writing_tag_for_sector_1_not_continuing,
outputOptical.ErrorMessage,
doneSectors + track.StartSector));
return ErrorNumber.WriteError;
}
}
doneSectors += sectorsToDo;
}
currentTrack++;
}
EndProgress?.Invoke();
EndProgress2?.Invoke();
if(errno != ErrorNumber.NoError && !_force) return errno;
}
return ErrorNumber.NoError;
}
void DecryptDvdSector(ref byte[] sector, IOpticalMediaImage inputOptical, ulong sectorAddress, uint sectorsToDo,
PluginRegister plugins, ref byte[] generatedTitleKeys)
{
// Decrypts DVD sectors using CSS (Content Scramble System) decryption
// Retrieves decryption keys from sector tags or generates them from ISO9660 filesystem
// Only MPEG packets within sectors can be encrypted
// Only sectors which are MPEG packets can be encrypted.
if(!Mpeg.ContainsMpegPackets(sector, sectorsToDo)) return;
byte[] cmi, titleKey;
if(sectorsToDo == 1)
{
if(inputOptical.ReadSectorTag(sectorAddress, false, SectorTagType.DvdSectorCmi, out cmi) ==
ErrorNumber.NoError &&
inputOptical.ReadSectorTag(sectorAddress, false, SectorTagType.DvdTitleKeyDecrypted, out titleKey) ==
ErrorNumber.NoError)
sector = CSS.DecryptSector(sector, titleKey, cmi);
else
{
if(generatedTitleKeys == null) GenerateDvdTitleKeys(inputOptical, plugins, ref generatedTitleKeys);
if(generatedTitleKeys != null)
{
sector = CSS.DecryptSector(sector,
generatedTitleKeys.Skip((int)(5 * sectorAddress)).Take(5).ToArray(),
null);
}
}
}
else
{
if(inputOptical.ReadSectorsTag(sectorAddress, false, sectorsToDo, SectorTagType.DvdSectorCmi, out cmi) ==
ErrorNumber.NoError &&
inputOptical.ReadSectorsTag(sectorAddress,
false,
sectorsToDo,
SectorTagType.DvdTitleKeyDecrypted,
out titleKey) ==
ErrorNumber.NoError)
sector = CSS.DecryptSector(sector, titleKey, cmi, sectorsToDo);
else
{
if(generatedTitleKeys == null) GenerateDvdTitleKeys(inputOptical, plugins, ref generatedTitleKeys);
if(generatedTitleKeys != null)
{
sector = CSS.DecryptSector(sector,
generatedTitleKeys.Skip((int)(5 * sectorAddress))
.Take((int)(5 * sectorsToDo))
.ToArray(),
null,
sectorsToDo);
}
}
}
}
/// <summary>
/// Generates DVD CSS title keys from ISO9660 filesystem
/// Used when explicit title keys are not available in sector tags
/// Searches for ISO9660 partitions to derive decryption keys
/// </summary>
void GenerateDvdTitleKeys(IOpticalMediaImage inputOptical, PluginRegister plugins, ref byte[] generatedTitleKeys)
{
List<Partition> partitions = Partitions.GetAll(inputOptical);
partitions = partitions.FindAll(p =>
{
Filesystems.Identify(inputOptical, out List<string> idPlugins, p);
return idPlugins.Contains("iso9660 filesystem");
});
if(!plugins.ReadOnlyFilesystems.TryGetValue("iso9660 filesystem", out IReadOnlyFilesystem rofs)) return;
AaruLogging.Debug(MODULE_NAME, UI.Generating_decryption_keys);
generatedTitleKeys = CSS.GenerateTitleKeys(inputOptical, partitions, inputOptical.Info.Sectors, rofs);
}
bool IsDvdMedia(MediaType mediaType) =>
// Checks if media type is any variant of DVD (ROM, R, RDL, PR, PRDL)
// Consolidates media type checking logic used throughout conversion process
mediaType is MediaType.DVDROM or MediaType.DVDR or MediaType.DVDRDL or MediaType.DVDPR or MediaType.DVDPRDL;
private bool IsCompactDiscMedia(MediaType mediaType) =>
// Checks if media type is any variant of compact disc (CD, CDDA, CDR, CDRW, etc.)
// Covers all 45+ CD-based media types including gaming and specialty formats
mediaType is MediaType.CD
or MediaType.CDDA
or MediaType.CDG
or MediaType.CDEG
or MediaType.CDI
or MediaType.CDROM
or MediaType.CDROMXA
or MediaType.CDPLUS
or MediaType.CDMO
or MediaType.CDR
or MediaType.CDRW
or MediaType.CDMRW
or MediaType.VCD
or MediaType.SVCD
or MediaType.PCD
or MediaType.DTSCD
or MediaType.CDMIDI
or MediaType.CDV
or MediaType.CDIREADY
or MediaType.FMTOWNS
or MediaType.PS1CD
or MediaType.PS2CD
or MediaType.MEGACD
or MediaType.SATURNCD
or MediaType.GDROM
or MediaType.GDR
or MediaType.MilCD
or MediaType.SuperCDROM2
or MediaType.JaguarCD
or MediaType.ThreeDO
or MediaType.PCFX
or MediaType.NeoGeoCD
or MediaType.CDTV
or MediaType.CD32
or MediaType.Playdia
or MediaType.Pippin
or MediaType.VideoNow
or MediaType.VideoNowColor
or MediaType.VideoNowXp
or MediaType.CVD;
}

View File

@@ -0,0 +1,229 @@
using System.Linq;
using Aaru.CommonTypes.Enums;
using Aaru.Localization;
using Aaru.Logging;
namespace Aaru.Core.Image;
public partial class Convert
{
ErrorNumber ConvertSectors(bool useLong, bool isTape)
{
InitProgress?.Invoke();
ErrorNumber errno = ErrorNumber.NoError;
ulong doneSectors = 0;
while(doneSectors < _inputImage.Info.Sectors)
{
byte[] sector;
uint sectorsToDo;
if(isTape)
sectorsToDo = 1;
else if(_inputImage.Info.Sectors - doneSectors >= _count)
sectorsToDo = _count;
else
sectorsToDo = (uint)(_inputImage.Info.Sectors - doneSectors);
UpdateProgress?.Invoke(string.Format(UI.Converting_sectors_0_to_1, doneSectors, doneSectors + sectorsToDo),
(long)doneSectors,
(long)_inputImage.Info.Sectors);
bool result;
SectorStatus sectorStatus = SectorStatus.NotDumped;
var sectorStatusArray = new SectorStatus[1];
if(useLong)
{
errno = sectorsToDo == 1
? _inputImage.ReadSectorLong(doneSectors, false, out sector, out sectorStatus)
: _inputImage.ReadSectorsLong(doneSectors,
false,
sectorsToDo,
out sector,
out sectorStatusArray);
if(errno == ErrorNumber.NoError)
{
result = sectorsToDo == 1
? _outputImage.WriteSectorLong(sector, doneSectors, false, sectorStatus)
: _outputImage.WriteSectorsLong(sector,
doneSectors,
false,
sectorsToDo,
sectorStatusArray);
}
else
{
result = true;
if(_force)
ErrorMessage?.Invoke(string.Format(UI.Error_0_reading_sector_1_continuing, errno, doneSectors));
else
{
StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_reading_sector_1_not_continuing,
errno,
doneSectors));
return errno;
}
}
}
else
{
errno = sectorsToDo == 1
? _inputImage.ReadSector(doneSectors, false, out sector, out sectorStatus)
: _inputImage.ReadSectors(doneSectors,
false,
sectorsToDo,
out sector,
out sectorStatusArray);
if(errno == ErrorNumber.NoError)
{
result = sectorsToDo == 1
? _outputImage.WriteSector(sector, doneSectors, false, sectorStatus)
: _outputImage.WriteSectors(sector,
doneSectors,
false,
sectorsToDo,
sectorStatusArray);
}
else
{
result = true;
if(_force)
ErrorMessage?.Invoke(string.Format(UI.Error_0_reading_sector_1_continuing, errno, doneSectors));
else
{
StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_reading_sector_1_not_continuing,
errno,
doneSectors));
return errno;
}
}
}
if(!result)
{
if(_force)
{
ErrorMessage?.Invoke(string.Format(UI.Error_0_writing_sector_1_continuing,
_outputImage.ErrorMessage,
doneSectors));
}
else
{
StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_writing_sector_1_not_continuing,
_outputImage.ErrorMessage,
doneSectors));
return ErrorNumber.WriteError;
}
}
doneSectors += sectorsToDo;
}
return ErrorNumber.NoError;
}
ErrorNumber ConvertSectorsTags(bool useLong)
{
foreach(SectorTagType tag in _inputImage.Info.ReadableSectorTags.TakeWhile(_ => useLong))
{
switch(tag)
{
case SectorTagType.AppleSonyTag:
case SectorTagType.AppleProfileTag:
case SectorTagType.PriamDataTowerTag:
case SectorTagType.CdSectorSync:
case SectorTagType.CdSectorHeader:
case SectorTagType.CdSectorSubHeader:
case SectorTagType.CdSectorEdc:
case SectorTagType.CdSectorEccP:
case SectorTagType.CdSectorEccQ:
case SectorTagType.CdSectorEcc:
// These tags are inline in long sector
continue;
}
if(_force && !_outputImage.SupportedSectorTags.Contains(tag)) continue;
ulong doneSectors = 0;
ErrorNumber errno;
InitProgress?.Invoke();
while(doneSectors < _inputImage.Info.Sectors)
{
uint sectorsToDo;
if(_inputImage.Info.Sectors - doneSectors >= _count)
sectorsToDo = _count;
else
sectorsToDo = (uint)(_inputImage.Info.Sectors - doneSectors);
UpdateProgress?.Invoke(string.Format(UI.Converting_tag_2_for_sectors_0_to_1,
doneSectors,
doneSectors + sectorsToDo,
tag),
(long)doneSectors,
(long)_inputImage.Info.Sectors);
bool result;
errno = sectorsToDo == 1
? _inputImage.ReadSectorTag(doneSectors, false, tag, out byte[] sector)
: _inputImage.ReadSectorsTag(doneSectors, false, sectorsToDo, tag, out sector);
if(errno == ErrorNumber.NoError)
{
result = sectorsToDo == 1
? _outputImage.WriteSectorTag(sector, doneSectors, false, tag)
: _outputImage.WriteSectorsTag(sector, doneSectors, false, sectorsToDo, tag);
}
else
{
result = true;
if(_force)
AaruLogging.Error(UI.Error_0_reading_sector_1_continuing, errno, doneSectors);
else
{
AaruLogging.Error(UI.Error_0_reading_sector_1_not_continuing, errno, doneSectors);
return errno;
}
}
if(!result)
{
if(_force)
{
AaruLogging.Error(UI.Error_0_writing_sector_1_continuing,
_outputImage.ErrorMessage,
doneSectors);
}
else
{
AaruLogging.Error(UI.Error_0_writing_sector_1_not_continuing,
_outputImage.ErrorMessage,
doneSectors);
return ErrorNumber.WriteError;
}
}
doneSectors += sectorsToDo;
}
EndProgress?.Invoke();
}
return ErrorNumber.NoError;
}
}

View File

@@ -0,0 +1,63 @@
using System.Linq;
using Aaru.CommonTypes.Enums;
using Aaru.Localization;
using Aaru.Logging;
using Humanizer;
namespace Aaru.Core.Image;
public partial class Convert
{
/// <summary>
/// Converts media tags (TOC, lead-in, etc.) from input to output format
/// Handles force mode to skip unsupported tags or fail on data loss
/// Shows progress for each tag being converted
/// </summary>
/// <returns>Status code</returns>
ErrorNumber ConvertMediaTags()
{
InitProgress?.Invoke();
ErrorNumber errorNumber = ErrorNumber.NoError;
foreach(MediaTagType mediaTag in _inputImage.Info.ReadableMediaTags.Where(mediaTag => !_force ||
_outputImage.SupportedMediaTags.Contains(mediaTag)))
{
PulseProgress?.Invoke(string.Format(UI.Converting_media_tag_0, mediaTag.Humanize()));
ErrorNumber errno = _inputImage.ReadMediaTag(mediaTag, out byte[] tag);
if(errno != ErrorNumber.NoError)
{
if(_force)
ErrorMessage?.Invoke(string.Format(UI.Error_0_reading_media_tag, errno));
else
{
AaruLogging.Error(UI.Error_0_reading_media_tag_not_continuing, errno);
errorNumber = errno;
break;
}
continue;
}
if(_outputImage?.WriteMediaTag(tag, mediaTag) == true) continue;
if(_force)
ErrorMessage?.Invoke(string.Format(UI.Error_0_writing_media_tag, _outputImage?.ErrorMessage));
else
{
StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_writing_media_tag_not_continuing,
_outputImage?.ErrorMessage));
errorNumber = ErrorNumber.WriteError;
break;
}
}
EndProgress?.Invoke();
return errorNumber;
}
}

View File

@@ -0,0 +1,42 @@
using System;
using Aaru.CommonTypes.Enums;
using Aaru.CommonTypes.Interfaces;
using Aaru.Localization;
namespace Aaru.Core.Image;
public partial class Convert
{
ErrorNumber ValidateTapeImage(ITapeImage inputTape, IWritableTapeImage outputTape)
{
// Validates tape image format compatibility
// Checks if input is tape-based but output format doesn't support tape images
// Returns error if unsupported media type combination detected
if(inputTape?.IsTape != true || outputTape is not null) return ErrorNumber.NoError;
StoppingErrorMessage?.Invoke(UI.Input_format_contains_a_tape_image_and_is_not_supported_by_output_format);
return ErrorNumber.UnsupportedMedia;
}
ErrorNumber SetupTapeImage(ITapeImage inputTape, IWritableTapeImage outputTape)
{
// Configures output format for tape image handling
// Calls SetTape() on output to initialize tape mode if both input and output support tapes
// Returns error if tape mode initialization fails
if(inputTape?.IsTape != true || outputTape == null) return ErrorNumber.NoError;
bool ret = outputTape.SetTape();
// Cannot set image to tape mode
if(ret) return ErrorNumber.NoError;
StoppingErrorMessage?.Invoke(UI.Error_setting_output_image_in_tape_mode +
Environment.NewLine +
_outputImage.ErrorMessage);
return ErrorNumber.WriteError;
}
}

View File

@@ -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,

View File

@@ -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)

View File

@@ -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,

View File

@@ -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
}
];

View File

@@ -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
},