Add ability to merge flux

This commit is contained in:
Rebecca Wallander
2026-04-04 12:28:21 +02:00
parent 6fd40158ea
commit 59b1235ac8
6 changed files with 182 additions and 72 deletions

View File

@@ -9,6 +9,24 @@ namespace Aaru.Core.Image;
public sealed partial class Merger
{
/// <summary>
/// Single extent covering sectors <c>0 .. sectorCount - 1</c>, or an empty list when
/// <paramref name="sectorCount" /> is zero (avoids <c>0 - 1</c> unsigned underflow in callers).
/// </summary>
static List<Extent> CreateDefaultDumpExtents(ulong sectorCount)
{
if(sectorCount == 0) return new List<Extent>();
return new List<Extent>
{
new Extent
{
Start = 0,
End = sectorCount - 1
}
};
}
List<ulong> CalculateSectorsToCopy(IMediaImage primaryImage, IMediaImage secondaryImage, Resume primaryResume,
Resume secondaryResume, List<ulong> overrideSectorsList)
{
@@ -16,14 +34,7 @@ public sealed partial class Merger
[
new DumpHardware
{
Extents =
[
new Extent
{
Start = 0,
End = primaryImage.Info.Sectors - 1
}
]
Extents = CreateDefaultDumpExtents(primaryImage.Info.Sectors)
}
];
@@ -32,14 +43,7 @@ public sealed partial class Merger
[
new DumpHardware
{
Extents =
[
new Extent
{
Start = 0,
End = secondaryImage.Info.Sectors - 1
}
]
Extents = CreateDefaultDumpExtents(secondaryImage.Info.Sectors)
}
];
@@ -93,14 +97,7 @@ public sealed partial class Merger
[
new DumpHardware
{
Extents =
[
new Extent
{
Start = 0,
End = primaryImage.Info.Sectors - 1
}
]
Extents = CreateDefaultDumpExtents(primaryImage.Info.Sectors)
}
];
@@ -109,14 +106,7 @@ public sealed partial class Merger
[
new DumpHardware
{
Extents =
[
new Extent
{
Start = 0,
End = secondaryImage.Info.Sectors - 1
}
]
Extents = CreateDefaultDumpExtents(secondaryImage.Info.Sectors)
}
];

View File

@@ -1,52 +1,140 @@
using System.Collections.Generic;
using System.Linq;
using Aaru.CommonTypes.Enums;
using Aaru.CommonTypes.Interfaces;
using Aaru.CommonTypes.Structs;
namespace Aaru.Core.Image;
public sealed partial class Merger
{
// TODO: Should we return error any time?
// TODO: Add progress reporting
ErrorNumber CopyFlux(IFluxImage inputFlux, IWritableFluxImage outputFlux)
ErrorNumber MergeFlux(IFluxImage primaryFlux, IFluxImage secondaryFlux, IWritableFluxImage outputFlux)
{
for(ushort track = 0; track < inputFlux.Info.Cylinders; track++)
ErrorNumber error = primaryFlux.GetAllFluxCaptures(out List<FluxCapture> primaryCaptures);
if(error != ErrorNumber.NoError) return error;
if(primaryCaptures is null) primaryCaptures = new List<FluxCapture>();
List<FluxCapture> secondaryCaptures = new List<FluxCapture>();
if(secondaryFlux != null)
{
for(uint head = 0; head < inputFlux.Info.Heads; head++)
error = secondaryFlux.GetAllFluxCaptures(out secondaryCaptures);
if(error != ErrorNumber.NoError) return error;
if(secondaryCaptures is null) secondaryCaptures = new List<FluxCapture>();
}
Dictionary<(uint Head, ushort Track, byte SubTrack), List<FluxCapture>> primaryByLocation =
GroupFluxCapturesByLocation(primaryCaptures);
Dictionary<(uint Head, ushort Track, byte SubTrack), List<FluxCapture>> secondaryByLocation =
GroupFluxCapturesByLocation(secondaryCaptures);
HashSet<(uint Head, ushort Track, byte SubTrack)> allKeys = new HashSet<(uint Head, ushort Track, byte SubTrack)>();
foreach((uint Head, ushort Track, byte SubTrack) key in primaryByLocation.Keys) allKeys.Add(key);
foreach((uint Head, ushort Track, byte SubTrack) key in secondaryByLocation.Keys) allKeys.Add(key);
List<(uint Head, ushort Track, byte SubTrack)> sortedKeys =
allKeys.OrderBy(static k => k.Track).ThenBy(static k => k.Head).ThenBy(static k => k.SubTrack).ToList();
foreach((uint Head, ushort Track, byte SubTrack) key in sortedKeys)
{
List<FluxCapture> primaryGroup =
primaryByLocation.TryGetValue(key, out List<FluxCapture> pg) ? pg : new List<FluxCapture>();
List<FluxCapture> secondaryGroup =
secondaryByLocation.TryGetValue(key, out List<FluxCapture> sg) ? sg : new List<FluxCapture>();
primaryGroup.Sort((FluxCapture a, FluxCapture b) => a.CaptureIndex.CompareTo(b.CaptureIndex));
secondaryGroup.Sort((FluxCapture a, FluxCapture b) => a.CaptureIndex.CompareTo(b.CaptureIndex));
uint outputIndex = 0;
foreach(FluxCapture capture in primaryGroup)
{
ErrorNumber error = inputFlux.SubTrackLength(head, track, out byte subTrackLen);
error = primaryFlux.ReadFluxCapture(capture.Head,
capture.Track,
capture.SubTrack,
capture.CaptureIndex,
out ulong indexResolution,
out ulong dataResolution,
out byte[] indexBuffer,
out byte[] dataBuffer);
if(error != ErrorNumber.NoError) continue;
if(error != ErrorNumber.NoError) return error;
for(byte subTrackIndex = 0; subTrackIndex < subTrackLen; subTrackIndex++)
error = outputFlux.WriteFluxCapture(indexResolution,
dataResolution,
indexBuffer,
dataBuffer,
capture.Head,
capture.Track,
capture.SubTrack,
outputIndex);
if(error != ErrorNumber.NoError) return error;
outputIndex++;
}
if(secondaryFlux != null)
{
foreach(FluxCapture capture in secondaryGroup)
{
error = inputFlux.CapturesLength(head, track, subTrackIndex, out uint capturesLen);
error = secondaryFlux.ReadFluxCapture(capture.Head,
capture.Track,
capture.SubTrack,
capture.CaptureIndex,
out ulong indexResolution,
out ulong dataResolution,
out byte[] indexBuffer,
out byte[] dataBuffer);
if(error != ErrorNumber.NoError) continue;
if(error != ErrorNumber.NoError) return error;
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);
error = outputFlux.WriteFluxCapture(indexResolution,
dataResolution,
indexBuffer,
dataBuffer,
capture.Head,
capture.Track,
capture.SubTrack,
outputIndex);
outputFlux.WriteFluxCapture(indexResolution,
dataResolution,
indexBuffer,
dataBuffer,
head,
track,
subTrackIndex,
captureIndex);
}
if(error != ErrorNumber.NoError) return error;
outputIndex++;
}
}
}
return ErrorNumber.NoError;
}
}
static Dictionary<(uint Head, ushort Track, byte SubTrack), List<FluxCapture>> GroupFluxCapturesByLocation(
List<FluxCapture> captures)
{
Dictionary<(uint Head, ushort Track, byte SubTrack), List<FluxCapture>> result =
new Dictionary<(uint Head, ushort Track, byte SubTrack), List<FluxCapture>>();
foreach(FluxCapture capture in captures)
{
(uint Head, ushort Track, byte SubTrack) key = (capture.Head, capture.Track, capture.SubTrack);
if(!result.TryGetValue(key, out List<FluxCapture> list))
{
list = new List<FluxCapture>();
result[key] = list;
}
list.Add(capture);
}
return result;
}
}

View File

@@ -220,12 +220,15 @@ public sealed partial class Merger
InitProgress?.Invoke();
PulseProgress?.Invoke(UI.Calculating_sectors_to_merge);
List<ulong> sectorsToCopyFromSecondImage =
CalculateSectorsToCopy(primaryImage, secondaryImage, primaryResume, secondaryResume, overrideSectorsList);
List<ulong> sectorsToCopyFromSecondImage =
CalculateSectorsToCopy(primaryImage, secondaryImage, primaryResume, secondaryResume, overrideSectorsList);
EndProgress?.Invoke();
if(sectorsToCopyFromSecondImage.Count == 0)
// Flux images might contain no decoded data, which results in a sector count of 0. We allow this if the image contains flux.
var containsFlux = primaryImage is IFluxImage || secondaryImage is IFluxImage;
if(sectorsToCopyFromSecondImage.Count == 0 && !containsFlux)
{
StoppingErrorMessage
?.Invoke(UI.No_sectors_to_merge__output_image_will_be_identical_to_primary_image_not_continuing);
@@ -344,10 +347,15 @@ public sealed partial class Merger
if(errno != ErrorNumber.NoError) return errno;
if(primaryImage is IFluxImage inputFlux && outputFormat is IWritableFluxImage outputFlux)
if(primaryImage is IFluxImage primaryFlux && outputFormat is IWritableFluxImage outputFlux)
{
UpdateStatus?.Invoke(UI.Flux_data_will_be_copied_as_is_from_primary_image);
errno = CopyFlux(inputFlux, outputFlux);
IFluxImage secondaryFlux = secondaryImage as IFluxImage;
UpdateStatus?.Invoke(secondaryFlux != null
? UI.Flux_merge_primary_then_secondary_appended
: UI.Flux_merge_primary_flux_only_secondary_not_flux_image);
errno = MergeFlux(primaryFlux, secondaryFlux, outputFlux);
if(errno != ErrorNumber.NoError) return errno;
}

View File

@@ -5115,6 +5115,24 @@ namespace Aaru.Localization {
return ResourceManager.GetString("Flux_data_will_be_copied_as_is_from_primary_image", resourceCulture);
}
}
/// <summary>
/// Looks up localized string similar to Merging flux: primary captures first, then secondary captures appended with renumbered indices...
/// </summary>
public static string Flux_merge_primary_then_secondary_appended {
get {
return ResourceManager.GetString("Flux_merge_primary_then_secondary_appended", resourceCulture);
}
}
/// <summary>
/// Looks up localized string similar to Secondary image is not a flux image; copying flux captures from the primary image only...
/// </summary>
public static string Flux_merge_primary_flux_only_secondary_not_flux_image {
get {
return ResourceManager.GetString("Flux_merge_primary_flux_only_secondary_not_flux_image", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Force geometry, only supported in not tape block media. Specify as C/H/S..

View File

@@ -5452,8 +5452,11 @@ Probadores:
<data name="Will_copy_0_sectors_from_secondary_image" xml:space="preserve">
<value>[slateblue1]Se copiarán [teal]{0}[/] sectores de la imagen secundaria.[/]</value>
</data>
<data name="Flux_data_will_be_copied_as_is_from_primary_image" xml:space="preserve">
<value>[slateblue1]Los datos de flujo se copiarán tal cual de la imagen primaria...[/]</value>
<data name="Flux_merge_primary_then_secondary_appended" xml:space="preserve">
<value>[slateblue1]Combinando flujo: primero capturas de la imagen primaria, luego las de la secundaria con índices renumerados...[/]</value>
</data>
<data name="Flux_merge_primary_flux_only_secondary_not_flux_image" xml:space="preserve">
<value>[slateblue1]La imagen secundaria no es de flujo; copiando solo las capturas de flujo de la imagen primaria...[/]</value>
</data>
<data name="Copying_file_0_of_partition_1" xml:space="preserve">
<value>[slateblue1]Copiando fichero [lime]{0}[/] de partición [teal]{1}[/]...[/]</value>

View File

@@ -5536,8 +5536,11 @@ Do you want to continue?</value>
<data name="Will_copy_0_sectors_from_secondary_image" xml:space="preserve">
<value>[slateblue1]Will copy [teal]{0}[/] sectors from secondary image.[/]</value>
</data>
<data name="Flux_data_will_be_copied_as_is_from_primary_image" xml:space="preserve">
<value>[slateblue1]Flux data will be copied as-is from primary image...[/]</value>
<data name="Flux_merge_primary_then_secondary_appended" xml:space="preserve">
<value>[slateblue1]Merging flux: primary captures first, then secondary captures appended with renumbered indices...[/]</value>
</data>
<data name="Flux_merge_primary_flux_only_secondary_not_flux_image" xml:space="preserve">
<value>[slateblue1]Secondary image is not a flux image; copying flux captures from the primary image only...[/]</value>
</data>
<data name="Copying_file_0_of_partition_1" xml:space="preserve">
<value>[slateblue1]Copying file [lime]{0}[/] of partition [teal]{1}[/]...[/]</value>