From 610ae3fe6130da70173daef8da25aac3ced95b14 Mon Sep 17 00:00:00 2001 From: Natalia Portillo Date: Fri, 24 Oct 2025 16:40:47 +0100 Subject: [PATCH] Convert negative and overflow sectors. --- Aaru/Commands/Image/Convert.cs | 477 ++++++++++++++++++++++++++++++++- 1 file changed, 471 insertions(+), 6 deletions(-) diff --git a/Aaru/Commands/Image/Convert.cs b/Aaru/Commands/Image/Convert.cs index 300722523..c5fa5562f 100644 --- a/Aaru/Commands/Image/Convert.cs +++ b/Aaru/Commands/Image/Convert.cs @@ -174,6 +174,9 @@ sealed class ConvertImageCommand : Command else AaruLogging.WriteLine(UI.Input_image_format_identified_by_0, inputFormat.Name); + uint nominalNegativeSectors = 0; + uint nominalOverflowSectors = 0; + try { // Open the input image file for reading @@ -193,6 +196,9 @@ sealed class ConvertImageCommand : Command return (int)opened; } + nominalNegativeSectors = settings.IgnoreNegativeSectors ? 0 : inputFormat.Info.NegativeSectors; + nominalOverflowSectors = settings.IgnoreOverflowSectors ? 0 : inputFormat.Info.OverflowSectors; + // Get media type and handle obsolete type mappings for backwards compatibility mediaType = inputFormat.Info.MediaType; @@ -306,7 +312,9 @@ sealed class ConvertImageCommand : Command settings.OutputPath, mediaType, parsedOptions, - inputFormat); + inputFormat, + nominalNegativeSectors, + nominalOverflowSectors); if(createResult != (int)ErrorNumber.NoError) return createResult; @@ -1285,6 +1293,27 @@ sealed class ConvertImageCommand : Command 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 => @@ -1548,6 +1577,8 @@ sealed class ConvertImageCommand : Command AaruLogging.Debug(MODULE_NAME, "--generate-subchannels={0}", settings.GenerateSubchannels); AaruLogging.Debug(MODULE_NAME, "--decrypt={0}", settings.Decrypt); AaruLogging.Debug(MODULE_NAME, "--aaru-metadata={0}", Markup.Escape(settings.AaruMetadata ?? "")); + AaruLogging.Debug(MODULE_NAME, "--ignore-negative-sectors={0}", settings.IgnoreNegativeSectors); + AaruLogging.Debug(MODULE_NAME, "--ignore-overflow-sectors={0}", settings.IgnoreOverflowSectors); AaruLogging.Debug(MODULE_NAME, UI.Parsed_options); @@ -1782,8 +1813,9 @@ sealed class ConvertImageCommand : Command return (int)ErrorNumber.NoError; } - private int CreateOutputImage(IWritableImage outputFormat, string outputPath, MediaType mediaType, - Dictionary parsedOptions, IMediaImage inputFormat) + 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 @@ -1801,8 +1833,8 @@ sealed class ConvertImageCommand : Command mediaType, parsedOptions, inputFormat.Info.Sectors, - 0, - 0, + negativeSectors, + overflowSectors, inputFormat.Info.SectorSize); }); @@ -1959,7 +1991,432 @@ sealed class ConvertImageCommand : Command return candidates[0]; } -#region Nested type: Settings + 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("Converting 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("[olive]Error [orange3]{0}[/] reading sector [lime]-{1}[/], continuing...", + errno, + i); + } + else + { + AaruLogging + .Error("[red]Error [orange3]{0}[/] reading sector [lime]-{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("[olive]Error [orange3]{0}[/] reading sector [lime]-{1}[/], continuing...", + errno, + i); + } + else + { + AaruLogging + .Error("[red]Error [orange3]{0}[/] reading sector [lime]-{1}[/], not continuing...", + errno, + i); + + return; + } + } + } + + if(!result) + { + if(settings.Force) + { + AaruLogging + .Error("[olive]Error [orange3]{0}[/] writing sector [lime]-{1}[/], continuing...[/]", + outputMedia.ErrorMessage, + i); + } + else + { + AaruLogging + .Error("[red]Error [orange3]{0}[/] writing sector [lime]-{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: + // This tags are inline in long sector + 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("[slateblue1]Converting tag [orange3]{1}[/] for sector [lime]-{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("[olive]Error [orange3]{0}[/] reading sector [lime]-{1}[/], continuing...", + errno, + i); + } + else + { + AaruLogging + .Error("[red]Error [orange3]{0}[/] reading sector [lime]-{1}[/], not continuing...", + errno, + i); + + return; + } + } + + if(!result) + { + if(settings.Force) + { + AaruLogging + .Error("[olive]Error [orange3]{0}[/] writing sector [lime]-{1}[/], continuing...[/]", + outputMedia.ErrorMessage, + i); + } + else + { + AaruLogging + .Error("[red]Error [orange3]{0}[/] writing sector [lime]-{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("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("[olive]Error [orange3]{0}[/] reading overflow sector [lime]{1}[/], continuing...", + errno, + i); + } + else + { + AaruLogging + .Error("[red]Error [orange3]{0}[/] reading overflow sector [lime]{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("[olive]Error [orange3]{0}[/] reading overflow sector [lime]{1}[/], continuing...", + errno, + i); + } + else + { + AaruLogging + .Error("[red]Error [orange3]{0}[/] reading overflow sector [lime]{1}[/], not continuing...", + errno, + i); + + return; + } + } + } + + if(!result) + { + if(settings.Force) + { + AaruLogging + .Error("[olive]Error [orange3]{0}[/] writing overflow sector [lime]{1}[/], continuing...[/]", + outputMedia.ErrorMessage, + i); + } + else + { + AaruLogging + .Error("[red]Error [orange3]{0}[/] writing overflow sector [lime]{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: + // This tags are inline in long sector + 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("[slateblue1]Converting tag [orange3]{1}[/] for overflow sector [lime]{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("[olive]Error [orange3]{0}[/] reading overflow sector [lime]{1}[/], continuing...", + errno, + inputFormat.Info.Sectors + i); + } + else + { + AaruLogging + .Error("[red]Error [orange3]{0}[/] reading overflow sector [lime]{1}[/], not continuing...", + errno, + inputFormat.Info.Sectors + i); + + return; + } + } + + if(!result) + { + if(settings.Force) + { + AaruLogging + .Error("[olive]Error [orange3]{0}[/] writing overflow sector [lime]{1}[/], continuing...[/]", + outputMedia.ErrorMessage, + inputFormat.Info.Sectors + i); + } + else + { + AaruLogging + .Error("[red]Error [orange3]{0}[/] writing overflow sector [lime]{1}[/], not continuing...[/]", + outputMedia.ErrorMessage, + inputFormat.Info.Sectors + i); + + errno = ErrorNumber.WriteError; + + return; + } + } + + tagsTask.Value++; + } + + tagsTask.StopTask(); + } + }); + + return (int)errno; + } public class Settings : ImageFamily { @@ -2077,6 +2534,14 @@ sealed class ConvertImageCommand : Command [Description("Output image path")] [CommandArgument(1, "")] public string OutputPath { get; init; } + [Description("Ignore negative sectors.")] + [DefaultValue(false)] + [CommandOption("--ignore-negative-sectors")] + public bool IgnoreNegativeSectors { get; init; } + [Description("Ignore overflow sectors.")] + [DefaultValue(false)] + [CommandOption("--ignore-overflow-sectors")] + public bool IgnoreOverflowSectors { get; init; } } #endregion