From 0998eb01aada39d7d1505ff1fd5ad07b88167b34 Mon Sep 17 00:00:00 2001 From: Rebecca Wallander Date: Sat, 18 Oct 2025 14:59:20 +0200 Subject: [PATCH 1/5] Add flux to aaruformat --- Aaru.CommonTypes/Interfaces/IFluxImage.cs | 10 + Aaru.CommonTypes/Structs/Images.cs | 10 + Aaru.Core/Image/Convert/Flux.cs | 59 ++--- Aaru.Core/Partitions.cs | 2 +- Aaru.Gui/Models/FluxCaptureModel.cs | 44 ++++ .../ViewModels/Panels/ImageInfoViewModel.cs | 16 ++ Aaru.Gui/ViewModels/Tabs/FluxInfoViewModel.cs | 66 +++++ Aaru.Gui/Views/Panels/ImageInfo.xaml | 7 + Aaru.Gui/Views/Tabs/FluxInfo.xaml | 156 +++++++++++ Aaru.Gui/Views/Tabs/FluxInfo.xaml.cs | 44 ++++ Aaru.Images/A2R/Read.cs | 34 +++ Aaru.Images/AaruFormat/AaruFormat.cs | 2 +- Aaru.Images/AaruFormat/Enums.cs | 11 +- Aaru.Images/AaruFormat/Flux.cs | 242 ++++++++++++++++++ Aaru.Images/AaruFormat/Helpers.cs | 1 + Aaru.Images/AaruFormat/Structs.cs | 15 ++ Aaru.Images/SuperCardPro/Read.cs | 34 +++ 17 files changed, 716 insertions(+), 37 deletions(-) create mode 100644 Aaru.Gui/Models/FluxCaptureModel.cs create mode 100644 Aaru.Gui/ViewModels/Tabs/FluxInfoViewModel.cs create mode 100644 Aaru.Gui/Views/Tabs/FluxInfo.xaml create mode 100644 Aaru.Gui/Views/Tabs/FluxInfo.xaml.cs create mode 100644 Aaru.Images/AaruFormat/Flux.cs diff --git a/Aaru.CommonTypes/Interfaces/IFluxImage.cs b/Aaru.CommonTypes/Interfaces/IFluxImage.cs index 4e6f1cd91..794b348cc 100644 --- a/Aaru.CommonTypes/Interfaces/IFluxImage.cs +++ b/Aaru.CommonTypes/Interfaces/IFluxImage.cs @@ -36,8 +36,10 @@ // Copyright © 2011-2026 Rebecca Wallander // ****************************************************************************/ +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using Aaru.CommonTypes.Enums; +using Aaru.CommonTypes.Structs; namespace Aaru.CommonTypes.Interfaces; @@ -127,4 +129,12 @@ public interface IFluxImage : IBaseImage /// Physical track (position of the heads over the floppy media, 0-based) /// The number of captures ErrorNumber SubTrackLength(uint head, ushort track, out byte length); + + /// + /// Returns a list of all flux captures in the image. This provides a convenient way to enumerate all captures + /// without needing to know the geometry ahead of time. + /// + /// List of all flux captures in the image, or null if an error occurred + /// Error number + ErrorNumber GetAllFluxCaptures(out List captures); } \ No newline at end of file diff --git a/Aaru.CommonTypes/Structs/Images.cs b/Aaru.CommonTypes/Structs/Images.cs index f74105b40..000dab483 100644 --- a/Aaru.CommonTypes/Structs/Images.cs +++ b/Aaru.CommonTypes/Structs/Images.cs @@ -257,4 +257,14 @@ public class LinearMemoryInterleave /// How many bytes in memory to skip every device byte public uint Value { get; set; } +} + +public class FluxCapture +{ + public uint Head; + public ushort Track; + public byte SubTrack; + public uint CaptureIndex; + public ulong IndexResolution; + public ulong DataResolution; } \ No newline at end of file diff --git a/Aaru.Core/Image/Convert/Flux.cs b/Aaru.Core/Image/Convert/Flux.cs index 9f0361a5a..66f6a7ed8 100644 --- a/Aaru.Core/Image/Convert/Flux.cs +++ b/Aaru.Core/Image/Convert/Flux.cs @@ -1,5 +1,7 @@ +using System.Collections.Generic; using Aaru.CommonTypes.Enums; using Aaru.CommonTypes.Interfaces; +using Aaru.CommonTypes.Structs; namespace Aaru.Core.Image; @@ -9,42 +11,33 @@ public partial class Convert // TODO: Add progress reporting ErrorNumber ConvertFlux(IFluxImage inputFlux, IWritableFluxImage outputFlux) { - for(ushort track = 0; track < inputFlux.Info.Cylinders; track++) + ErrorNumber error = inputFlux.GetAllFluxCaptures(out List captures); + + if(error != ErrorNumber.NoError) return error; + + if(captures is null || captures.Count == 0) return ErrorNumber.NoError; + + foreach(FluxCapture capture in captures) { - for(uint head = 0; head < inputFlux.Info.Heads; head++) - { - ErrorNumber error = inputFlux.SubTrackLength(head, track, out byte subTrackLen); + error = inputFlux.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) 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); - } - } - } + outputFlux.WriteFluxCapture(indexResolution, + dataResolution, + indexBuffer, + dataBuffer, + capture.Head, + capture.Track, + capture.SubTrack, + capture.CaptureIndex); } return ErrorNumber.NoError; diff --git a/Aaru.Core/Partitions.cs b/Aaru.Core/Partitions.cs index e4cf4f6e4..bf377f8ff 100644 --- a/Aaru.Core/Partitions.cs +++ b/Aaru.Core/Partitions.cs @@ -98,7 +98,7 @@ public static class Partitions } // Getting all partitions at start of device - if(!checkedLocations.Contains(0)) + if(!checkedLocations.Contains(0) && image.Info.Sectors > 0) { foreach(IPartition plugin in plugins.Partitions.Values) { diff --git a/Aaru.Gui/Models/FluxCaptureModel.cs b/Aaru.Gui/Models/FluxCaptureModel.cs new file mode 100644 index 000000000..24fbe5de9 --- /dev/null +++ b/Aaru.Gui/Models/FluxCaptureModel.cs @@ -0,0 +1,44 @@ +// /*************************************************************************** +// Aaru Data Preservation Suite +// ---------------------------------------------------------------------------- +// +// Filename : FluxCaptureModel.cs +// Author(s) : Rebecca Wallander +// +// Component : GUI data models. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains information about flux captures. +// +// --[ License ] -------------------------------------------------------------- +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General public License for more details. +// +// You should have received a copy of the GNU General public License +// along with this program. If not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2025 Rebecca Wallander +// ****************************************************************************/ + +namespace Aaru.Gui.Models; + +public sealed class FluxCaptureModel +{ + public uint Head { get; set; } + public ushort Track { get; set; } + public byte SubTrack { get; set; } + public uint CaptureIndex { get; set; } + public ulong IndexResolution { get; set; } + public ulong DataResolution { get; set; } +} + diff --git a/Aaru.Gui/ViewModels/Panels/ImageInfoViewModel.cs b/Aaru.Gui/ViewModels/Panels/ImageInfoViewModel.cs index 5fbeeb835..7a2475f11 100644 --- a/Aaru.Gui/ViewModels/Panels/ImageInfoViewModel.cs +++ b/Aaru.Gui/ViewModels/Panels/ImageInfoViewModel.cs @@ -31,6 +31,7 @@ // ****************************************************************************/ using System; +using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Text; @@ -38,6 +39,7 @@ using System.Windows.Input; using Aaru.CommonTypes.AaruMetadata; using Aaru.CommonTypes.Enums; using Aaru.CommonTypes.Interfaces; +using Aaru.CommonTypes.Structs; using Aaru.CommonTypes.Structs.Devices.SCSI; using Aaru.Decoders.CD; using Aaru.Decoders.DVD; @@ -815,6 +817,19 @@ public sealed class ImageInfoViewModel : ViewModelBase } } + if(imageFormat is IFluxImage fluxImage) + { + ErrorNumber fluxError = fluxImage.GetAllFluxCaptures(out List fluxCaptures); + + if(fluxError == ErrorNumber.NoError && fluxCaptures is { Count: > 0 }) + { + FluxInfo = new FluxInfo + { + DataContext = new FluxInfoViewModel(fluxCaptures) + }; + } + } + if(imageFormat.DumpHardware is null) return; foreach(DumpHardware dump in imageFormat.DumpHardware) @@ -845,6 +860,7 @@ public sealed class ImageInfoViewModel : ViewModelBase public XboxInfo XboxInfo { get; } public PcmciaInfo PcmciaInfo { get; } public SdMmcInfo SdMmcInfo { get; } + public FluxInfo FluxInfo { get; } public IImage MediaLogo { get; } public string ImagePathText { get; } public string FilterText { get; } diff --git a/Aaru.Gui/ViewModels/Tabs/FluxInfoViewModel.cs b/Aaru.Gui/ViewModels/Tabs/FluxInfoViewModel.cs new file mode 100644 index 000000000..f7a67ec10 --- /dev/null +++ b/Aaru.Gui/ViewModels/Tabs/FluxInfoViewModel.cs @@ -0,0 +1,66 @@ +// /*************************************************************************** +// Aaru Data Preservation Suite +// ---------------------------------------------------------------------------- +// +// Filename : FluxInfoViewModel.cs +// Author(s) : Rebecca Wallander +// +// Component : GUI view models. +// +// --[ Description ] ---------------------------------------------------------- +// +// View model and code for the Flux information tab. +// +// --[ License ] -------------------------------------------------------------- +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General public License for more details. +// +// You should have received a copy of the GNU General public License +// along with this program. If not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2025 Rebecca Wallander +// ****************************************************************************/ + +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using Aaru.CommonTypes.Structs; +using Aaru.Gui.Models; + +namespace Aaru.Gui.ViewModels.Tabs; + +public sealed class FluxInfoViewModel : ViewModelBase +{ + public FluxInfoViewModel(List fluxCaptures) + { + FluxCaptures = []; + + if(fluxCaptures is { Count: > 0 }) + { + foreach(FluxCapture capture in fluxCaptures) + { + FluxCaptures.Add(new FluxCaptureModel + { + Head = capture.Head, + Track = capture.Track, + SubTrack = capture.SubTrack, + CaptureIndex = capture.CaptureIndex, + IndexResolution = capture.IndexResolution, + DataResolution = capture.DataResolution + }); + } + } + } + + public ObservableCollection FluxCaptures { get; } +} + diff --git a/Aaru.Gui/Views/Panels/ImageInfo.xaml b/Aaru.Gui/Views/Panels/ImageInfo.xaml index a7a86ff61..a1fad6ad0 100644 --- a/Aaru.Gui/Views/Panels/ImageInfo.xaml +++ b/Aaru.Gui/Views/Panels/ImageInfo.xaml @@ -734,6 +734,13 @@ + + + + + + +// +// Component : GUI tabs. +// +// ‐‐[ Description ] ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ +// +// Flux information tab. +// +// ‐‐[ License ] ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General public License for more details. +// +// You should have received a copy of the GNU General public License +// along with this program. If not, see . +// +// ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ +// Copyright © 2011-2025 Rebecca Wallander +// ****************************************************************************/ +--> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Aaru.Gui/Views/Tabs/FluxInfo.xaml.cs b/Aaru.Gui/Views/Tabs/FluxInfo.xaml.cs new file mode 100644 index 000000000..59438f274 --- /dev/null +++ b/Aaru.Gui/Views/Tabs/FluxInfo.xaml.cs @@ -0,0 +1,44 @@ +// /*************************************************************************** +// Aaru Data Preservation Suite +// ---------------------------------------------------------------------------- +// +// Filename : FluxInfo.xaml.cs +// Author(s) : Rebecca Wallander +// +// Component : GUI tabs. +// +// --[ Description ] ---------------------------------------------------------- +// +// Flux information tab. +// +// --[ License ] -------------------------------------------------------------- +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General public License for more details. +// +// You should have received a copy of the GNU General public License +// along with this program. If not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2025 Rebecca Wallander +// ****************************************************************************/ + +using Avalonia.Controls; +using Avalonia.Markup.Xaml; + +namespace Aaru.Gui.Views.Tabs; + +public sealed class FluxInfo : UserControl +{ + public FluxInfo() => InitializeComponent(); + + void InitializeComponent() => AvaloniaXamlLoader.Load(this); +} + diff --git a/Aaru.Images/A2R/Read.cs b/Aaru.Images/A2R/Read.cs index b6e8ab911..fcdaa3cfc 100644 --- a/Aaru.Images/A2R/Read.cs +++ b/Aaru.Images/A2R/Read.cs @@ -39,6 +39,7 @@ using System.Text; using Aaru.CommonTypes; using Aaru.CommonTypes.Enums; using Aaru.CommonTypes.Interfaces; +using Aaru.CommonTypes.Structs; using Aaru.Helpers; using Aaru.Logging; @@ -505,4 +506,37 @@ public sealed partial class A2R return _a2RCaptures.FindAll(capture => index == capture.location)[(int)captureIndex]; } + + /// + public ErrorNumber GetAllFluxCaptures(out List captures) + { + captures = []; + + if(_a2RCaptures is { Count: > 0 }) + { + // Group captures by head/track/subtrack to assign capture indices + var grouped = _a2RCaptures.GroupBy(c => new { c.head, c.track, c.subTrack }) + .ToList(); + + foreach(var group in grouped) + { + uint captureIndex = 0; + + foreach(StreamCapture streamCapture in group) + { + captures.Add(new FluxCapture + { + Head = streamCapture.head, + Track = streamCapture.track, + SubTrack = streamCapture.subTrack, + CaptureIndex = captureIndex++, + IndexResolution = streamCapture.resolution, + DataResolution = streamCapture.resolution + }); + } + } + } + + return ErrorNumber.NoError; + } } \ No newline at end of file diff --git a/Aaru.Images/AaruFormat/AaruFormat.cs b/Aaru.Images/AaruFormat/AaruFormat.cs index 1eb8e4ff1..28468e5bb 100644 --- a/Aaru.Images/AaruFormat/AaruFormat.cs +++ b/Aaru.Images/AaruFormat/AaruFormat.cs @@ -6,7 +6,7 @@ namespace Aaru.Images; /// /// Implements reading and writing AaruFormat media images -public sealed partial class AaruFormat : IWritableOpticalImage, IVerifiableImage, IWritableTapeImage, IDisposable +public sealed partial class AaruFormat : IWritableOpticalImage, IVerifiableImage, IWritableTapeImage, IWritableFluxImage, IDisposable { const string MODULE_NAME = "Aaru Format plugin"; IntPtr _context; diff --git a/Aaru.Images/AaruFormat/Enums.cs b/Aaru.Images/AaruFormat/Enums.cs index 7a366d58d..555443a04 100644 --- a/Aaru.Images/AaruFormat/Enums.cs +++ b/Aaru.Images/AaruFormat/Enums.cs @@ -40,7 +40,9 @@ public sealed partial class AaruFormat /// Block containing list of indexes for Compact Disc tracks CompactDiscIndexesBlock = 0x58494443, /// Block containing JSON version of Aaru Metadata - AaruMetadataJsonBlock = 0x444D534A + AaruMetadataJsonBlock = 0x444D534A, + /// Block containing list of flux captures + FluxCapturesBlock = 0x58554C46 } #endregion @@ -210,7 +212,12 @@ public sealed partial class AaruFormat /// Requested metadata not present in image. /// <remarks>AARUF_ERROR_METADATA_NOT_PRESENT</remarks> /// - MetadataNotPresent = -30 + MetadataNotPresent = -30, + /// + /// Requested flux data not present in image. + /// <remarks>AARUF_ERROR_FLUX_DATA_NOT_FOUND</remarks> + /// + FluxDataNotFound = -31 } #endregion diff --git a/Aaru.Images/AaruFormat/Flux.cs b/Aaru.Images/AaruFormat/Flux.cs new file mode 100644 index 000000000..cd94ff0ab --- /dev/null +++ b/Aaru.Images/AaruFormat/Flux.cs @@ -0,0 +1,242 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Aaru.CommonTypes.Enums; +using Aaru.CommonTypes.Structs; +using Marshal = System.Runtime.InteropServices.Marshal; + +namespace Aaru.Images; + +public sealed partial class AaruFormat +{ + List _fluxCaptures; + + #region IWritableFluxImage Members + + /// + public List FluxCaptures + { + get + { + if(_fluxCaptures is not null) return _fluxCaptures; + + nuint length = 0; + + Status res = aaruf_get_flux_captures(_context, null, ref length); + + if(res != Status.BufferTooSmall) + { + ErrorMessage = StatusToErrorMessage(res); + + return null; + } + + byte[] buffer = new byte[length]; + + res = aaruf_get_flux_captures(_context, buffer, ref length); + + if(res != Status.Ok) + { + ErrorMessage = StatusToErrorMessage(res); + + return null; + } + + int fluxCaptureSize = Marshal.SizeOf(); + int fluxCaptureCount = (int)length / fluxCaptureSize; + + _fluxCaptures = new List(fluxCaptureCount); + + IntPtr ptr = Marshal.AllocHGlobal(fluxCaptureCount * fluxCaptureSize); + + try + { + Marshal.Copy(buffer, 0, ptr, (int)length); + + for(int i = 0; i < fluxCaptureCount; i++) + { + nint fluxCapturePtr = IntPtr.Add(ptr, i * fluxCaptureSize); + FluxCaptureEntry entry = Marshal.PtrToStructure(fluxCapturePtr); + + var capture = new FluxCapture + { + Head = entry.Head, + Track = entry.Track, + SubTrack = entry.SubTrack, + CaptureIndex = entry.CaptureIndex, + IndexResolution = entry.IndexResolution, + DataResolution = entry.DataResolution, + }; + + _fluxCaptures.Add(capture); + } + } + catch + { + _fluxCaptures = null; +#pragma warning disable ERP022 + } +#pragma warning restore ERP022 + finally + { + Marshal.FreeHGlobal(ptr); + } + + return _fluxCaptures; + } + } + + /// + public ErrorNumber CapturesLength(uint head, ushort track, byte subTrack, out uint length) + { + length = (uint)FluxCaptures.FindAll(capture => capture.Head == head && capture.Track == track && capture.SubTrack == subTrack).Count; + + return ErrorNumber.NoError; + } + + /// + public ErrorNumber ReadFluxIndexResolution(uint head, ushort track, byte subTrack, uint captureIndex, out ulong resolution) + { + FluxCapture capture = FluxCaptures.Find(capture => capture.Head == head && capture.Track == track && capture.SubTrack == subTrack && capture.CaptureIndex == captureIndex); + + resolution = capture.IndexResolution; + + return ErrorNumber.NoError; + } + + /// + public ErrorNumber ReadFluxDataResolution(uint head, ushort track, byte subTrack, uint captureIndex, out ulong resolution) + { + FluxCapture capture = FluxCaptures.Find(capture => capture.Head == head && capture.Track == track && capture.SubTrack == subTrack && capture.CaptureIndex == captureIndex); + + resolution = capture.DataResolution; + + return ErrorNumber.NoError; + } + + /// + public ErrorNumber ReadFluxResolution(uint head, ushort track, byte subTrack, uint captureIndex, out ulong indexResolution, out ulong dataResolution) + { + FluxCapture capture = FluxCaptures.Find(capture => capture.Head == head && capture.Track == track && capture.SubTrack == subTrack && capture.CaptureIndex == captureIndex); + + indexResolution = capture.IndexResolution; + dataResolution = capture.DataResolution; + + return ErrorNumber.NoError; + } + + /// + public ErrorNumber ReadFluxCapture(uint head, ushort track, byte subTrack, uint captureIndex, out ulong indexResolution, out ulong dataResolution, out byte[] indexBuffer, out byte[] dataBuffer) + { + FluxCapture capture = FluxCaptures.Find(capture => capture.Head == head && capture.Track == track && capture.SubTrack == subTrack && capture.CaptureIndex == captureIndex); + + nuint indexLength = 0; + nuint dataLength = 0; + + Status res = aaruf_read_flux_capture(_context, head, track, subTrack, captureIndex, null, ref indexLength, null, ref dataLength); + + if(res != Status.BufferTooSmall) + { + indexResolution = 0; + dataResolution = 0; + indexBuffer = null; + dataBuffer = null; + return StatusToErrorNumber(res); + } + + indexBuffer = new byte[indexLength]; + dataBuffer = new byte[dataLength]; + + res = aaruf_read_flux_capture(_context, head, track, subTrack, captureIndex, indexBuffer, ref indexLength, dataBuffer, ref dataLength); + + if(res != Status.Ok) + { + indexResolution = 0; + dataResolution = 0; + indexBuffer = null; + dataBuffer = null; + return StatusToErrorNumber(res); + } + + indexResolution = capture.IndexResolution; + dataResolution = capture.DataResolution; + + return ErrorNumber.NoError; + } + + /// + public ErrorNumber ReadFluxIndexCapture(uint head, ushort track, byte subTrack, uint captureIndex, out byte[] buffer) + { + return ReadFluxCapture(head, track, subTrack, captureIndex, out _, out _, out buffer, out _); + } + + /// + public ErrorNumber ReadFluxDataCapture(uint head, ushort track, byte subTrack, uint captureIndex, out byte[] buffer) + { + return ReadFluxCapture(head, track, subTrack, captureIndex, out _, out _, out _, out buffer); + } + + /// + public ErrorNumber SubTrackLength(uint head, ushort track, out byte length) + { + length = (byte)(FluxCaptures.FindAll(capture => capture.Head == head && capture.Track == track).Max(capture => capture.SubTrack) + 1); + + return ErrorNumber.NoError; + } + + /// + public ErrorNumber GetAllFluxCaptures(out List captures) + { + captures = FluxCaptures; + + return ErrorNumber.NoError; + } + + /// + public ErrorNumber WriteFluxCapture(ulong indexResolution, ulong dataResolution, byte[] indexBuffer, byte[] dataBuffer, uint head, ushort track, byte subTrack, uint captureIndex) + { + Status res = aaruf_write_flux_capture(_context, head, track, subTrack, captureIndex, dataResolution, indexResolution, dataBuffer, (uint)dataBuffer.Length, indexBuffer, (uint)indexBuffer.Length); + + if(res != Status.Ok) return StatusToErrorNumber(res); + + return ErrorNumber.NoError; + } + + /// + public ErrorNumber WriteFluxIndexCapture(ulong resolution, byte[] index, uint head, ushort track, byte subTrack, uint captureIndex) + { + return ErrorNumber.NotImplemented; + } + + /// + public ErrorNumber WriteFluxDataCapture(ulong resolution, byte[] data, uint head, ushort track, byte subTrack, uint captureIndex) + { + return ErrorNumber.NotImplemented; + } + + #endregion + + // AARU_EXPORT int32_t AARU_CALL aaruf_get_flux_captures(void *context, uint8_t *buffer, size_t *length) + [LibraryImport("libaaruformat", EntryPoint = "aaruf_get_flux_captures", SetLastError = true)] + [UnmanagedCallConv(CallConvs = [typeof(CallConvStdcall)])] + private static partial Status aaruf_get_flux_captures(IntPtr context, byte[] buffer, ref nuint length); + + // AARU_EXPORT int32_t AARU_CALL aaruf_read_flux_capture(void *context, uint32_t head, uint16_t track, uint8_t subtrack, + // uint8_t capture_index, uint8_t *index_data, + // uint32_t *index_length, uint8_t *data_data, + // uint32_t *data_length) + [LibraryImport("libaaruformat", EntryPoint = "aaruf_read_flux_capture", SetLastError = true)] + [UnmanagedCallConv(CallConvs = [typeof(CallConvStdcall)])] + private static partial Status aaruf_read_flux_capture(IntPtr context, uint head, ushort track, byte subtrack, uint captureIndex, byte[] indexData, ref nuint indexLength, byte[] dataData, ref nuint dataLength); + + // AARU_EXPORT int32_t AARU_CALL aaruf_write_flux_capture(void *context, uint32_t head, uint16_t track, uint8_t subtrack, + // uint16_t capture_index, uint64_t data_resolution, + // uint64_t index_resolution, const uint8_t *data, + // uint32_t data_length, const uint8_t *index, + // uint32_t index_length); + [LibraryImport("libaaruformat", EntryPoint = "aaruf_write_flux_capture", SetLastError = true)] + [UnmanagedCallConv(CallConvs = [typeof(CallConvStdcall)])] + private static partial Status aaruf_write_flux_capture(IntPtr context, uint head, ushort track, byte subtrack, uint captureIndex, ulong dataResolution, ulong indexResolution, byte[] data, uint dataLength, byte[] index, uint indexLength); +} \ No newline at end of file diff --git a/Aaru.Images/AaruFormat/Helpers.cs b/Aaru.Images/AaruFormat/Helpers.cs index 4baf1c78e..c7fdabb66 100644 --- a/Aaru.Images/AaruFormat/Helpers.cs +++ b/Aaru.Images/AaruFormat/Helpers.cs @@ -92,6 +92,7 @@ public sealed partial class AaruFormat Status.TapeFileNotFound => "Requested tape file number is not present in image.", Status.TapePartitionNotFound => "Requested tape partition is not present in image.", Status.MetadataNotPresent => "Requested metadata is not present in image.", + Status.FluxDataNotFound => "Requested flux data is not present in image.", _ => "Unknown error occurred." }; } diff --git a/Aaru.Images/AaruFormat/Structs.cs b/Aaru.Images/AaruFormat/Structs.cs index 5bb2e6ce7..34d4e6d06 100644 --- a/Aaru.Images/AaruFormat/Structs.cs +++ b/Aaru.Images/AaruFormat/Structs.cs @@ -238,5 +238,20 @@ public sealed partial class AaruFormat public byte Flags; } +#endregion + +#region Nested type: FluxCaptureEntry + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct FluxCaptureEntry + { + public uint Head; + public ushort Track; + public byte SubTrack; + public uint CaptureIndex; + public ulong IndexResolution; + public ulong DataResolution; + } + #endregion } \ No newline at end of file diff --git a/Aaru.Images/SuperCardPro/Read.cs b/Aaru.Images/SuperCardPro/Read.cs index f5d2579a6..973d4bbf5 100644 --- a/Aaru.Images/SuperCardPro/Read.cs +++ b/Aaru.Images/SuperCardPro/Read.cs @@ -36,6 +36,7 @@ using System.IO; using Aaru.CommonTypes; using Aaru.CommonTypes.Enums; using Aaru.CommonTypes.Interfaces; +using Aaru.CommonTypes.Structs; using Aaru.Helpers; using Aaru.Logging; @@ -554,6 +555,39 @@ public sealed partial class SuperCardPro return indexCapture; } + /// + public ErrorNumber GetAllFluxCaptures(out List captures) + { + captures = []; + + if(ScpTracks is { Count: > 0 }) + { + ulong resolution = (ulong)((Header.resolution + 1) * DEFAULT_RESOLUTION); + + foreach(KeyValuePair kvp in ScpTracks) + { + byte scpTrack = kvp.Key; + + // Reverse HeadTrackSubToScpTrack: scpTrack = head + track * 2 + uint head = (uint)(scpTrack % 2); + ushort track = (ushort)(scpTrack / 2); + const byte subTrack = 0; // SuperCardPro always has subTrack = 0 + + captures.Add(new FluxCapture + { + Head = head, + Track = track, + SubTrack = subTrack, + CaptureIndex = 0, // SuperCardPro always has one capture per track + IndexResolution = resolution, + DataResolution = resolution + }); + } + } + + return ErrorNumber.NoError; + } + /// public ErrorNumber ReadMediaTag(MediaTagType tag, out byte[] buffer) { From edac07021842f41620c640469184a86a2730322a Mon Sep 17 00:00:00 2001 From: Rebecca Wallander Date: Sun, 21 Dec 2025 17:23:54 +0100 Subject: [PATCH 2/5] Address sonar issues --- Aaru.CommonTypes/Structs/Images.cs | 12 ++++++------ Aaru.Images/AaruFormat/Flux.cs | 4 ++-- Aaru.Images/SuperCardPro/Read.cs | 9 +++++---- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/Aaru.CommonTypes/Structs/Images.cs b/Aaru.CommonTypes/Structs/Images.cs index 000dab483..2c0ca79ba 100644 --- a/Aaru.CommonTypes/Structs/Images.cs +++ b/Aaru.CommonTypes/Structs/Images.cs @@ -261,10 +261,10 @@ public class LinearMemoryInterleave public class FluxCapture { - public uint Head; - public ushort Track; - public byte SubTrack; - public uint CaptureIndex; - public ulong IndexResolution; - public ulong DataResolution; + public uint Head { get; set; } + public ushort Track { get; set; } + public byte SubTrack { get; set; } + public uint CaptureIndex { get; set; } + public ulong IndexResolution { get; set; } + public ulong DataResolution { get; set; } } \ No newline at end of file diff --git a/Aaru.Images/AaruFormat/Flux.cs b/Aaru.Images/AaruFormat/Flux.cs index cd94ff0ab..7a913f7fa 100644 --- a/Aaru.Images/AaruFormat/Flux.cs +++ b/Aaru.Images/AaruFormat/Flux.cs @@ -30,7 +30,7 @@ public sealed partial class AaruFormat { ErrorMessage = StatusToErrorMessage(res); - return null; + return []; } byte[] buffer = new byte[length]; @@ -41,7 +41,7 @@ public sealed partial class AaruFormat { ErrorMessage = StatusToErrorMessage(res); - return null; + return []; } int fluxCaptureSize = Marshal.SizeOf(); diff --git a/Aaru.Images/SuperCardPro/Read.cs b/Aaru.Images/SuperCardPro/Read.cs index 973d4bbf5..5ad94677d 100644 --- a/Aaru.Images/SuperCardPro/Read.cs +++ b/Aaru.Images/SuperCardPro/Read.cs @@ -33,6 +33,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using Aaru.CommonTypes; using Aaru.CommonTypes.Enums; using Aaru.CommonTypes.Interfaces; @@ -564,7 +565,7 @@ public sealed partial class SuperCardPro { ulong resolution = (ulong)((Header.resolution + 1) * DEFAULT_RESOLUTION); - foreach(KeyValuePair kvp in ScpTracks) + captures = [.. ScpTracks.Select(kvp => { byte scpTrack = kvp.Key; @@ -573,7 +574,7 @@ public sealed partial class SuperCardPro ushort track = (ushort)(scpTrack / 2); const byte subTrack = 0; // SuperCardPro always has subTrack = 0 - captures.Add(new FluxCapture + return new FluxCapture { Head = head, Track = track, @@ -581,8 +582,8 @@ public sealed partial class SuperCardPro CaptureIndex = 0, // SuperCardPro always has one capture per track IndexResolution = resolution, DataResolution = resolution - }); - } + }; + })]; } return ErrorNumber.NoError; From 8020dee79dd5ee63bcd3e23abe9bf6cd7dd1a18e Mon Sep 17 00:00:00 2001 From: Rebecca Wallander Date: Fri, 26 Dec 2025 21:28:52 +0100 Subject: [PATCH 3/5] Add test suite for flux --- Aaru.Tests/Images/A2R.cs | 202 ++++++++++++++++++++++++ Aaru.Tests/Images/AaruFormat/V2Flux.cs | 105 ++++++++++++ Aaru.Tests/Images/FluxMediaImageTest.cs | 188 ++++++++++++++++++++++ Aaru.Tests/Images/SuperCardPro.cs | 105 ++++++++++++ Aaru.Tests/Structs.cs | 24 +++ 5 files changed, 624 insertions(+) create mode 100644 Aaru.Tests/Images/A2R.cs create mode 100644 Aaru.Tests/Images/AaruFormat/V2Flux.cs create mode 100644 Aaru.Tests/Images/FluxMediaImageTest.cs create mode 100644 Aaru.Tests/Images/SuperCardPro.cs diff --git a/Aaru.Tests/Images/A2R.cs b/Aaru.Tests/Images/A2R.cs new file mode 100644 index 000000000..04ee7f0be --- /dev/null +++ b/Aaru.Tests/Images/A2R.cs @@ -0,0 +1,202 @@ +// /*************************************************************************** +// Aaru Data Preservation Suite +// ---------------------------------------------------------------------------- +// +// Filename : A2R.cs +// Author(s) : Rebecca Wallander +// +// Component : Aaru unit testing. +// +// --[ License ] -------------------------------------------------------------- +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2026 Rebecca Wallander +// ****************************************************************************/ + +using System.IO; +using Aaru.CommonTypes.Interfaces; +using NUnit.Framework; + +namespace Aaru.Tests.Images; + +[TestFixture] +public class A2R : FluxMediaImageTest +{ + public override string DataFolder => Path.Combine(Consts.TestFilesRoot, "Media image formats", "A2R"); + public override IMediaImage Plugin => new Aaru.Images.A2R(); + + public override FluxImageTestExpected[] Tests => + [ + new() + { + TestFile = "The Quest.a2r", + FluxCaptureCount = 162, + FluxCaptures = [ + new FluxCaptureTestExpected + { + Head = 0, + Track = 0, + SubTrack = 0, + CaptureIndex = 0, + IndexResolution = 62500, + DataResolution = 62500 + }, + new FluxCaptureTestExpected + { + Head = 0, + Track = 0, + SubTrack = 0, + CaptureIndex = 1, + IndexResolution = 62500, + DataResolution = 62500 + }, + new FluxCaptureTestExpected + { + Head = 0, + Track = 1, + SubTrack = 0, + CaptureIndex = 0, + IndexResolution = 62500, + DataResolution = 62500 + }, + new FluxCaptureTestExpected + { + Head = 0, + Track = 1, + SubTrack = 0, + CaptureIndex = 1, + IndexResolution = 62500, + DataResolution = 62500 + }, + new FluxCaptureTestExpected + { + Head = 0, + Track = 1, + SubTrack = 0, + CaptureIndex = 2, + IndexResolution = 62500, + DataResolution = 62500 + }, + new FluxCaptureTestExpected + { + Head = 0, + Track = 1, + SubTrack = 0, + CaptureIndex = 3, + IndexResolution = 62500, + DataResolution = 62500 + }, + ] + }, + new() + { + TestFile = "Lotus 1-2-3 v2 - Utility Disk.a2r", + FluxCaptureCount = 320, + FluxCaptures = [ + new FluxCaptureTestExpected + { + Head = 0, + Track = 0, + SubTrack = 0, + CaptureIndex = 0, + IndexResolution = 62500, + DataResolution = 62500 + }, + new FluxCaptureTestExpected + { + Head = 0, + Track = 0, + SubTrack = 0, + CaptureIndex = 1, + IndexResolution = 62500, + DataResolution = 62500 + }, + new FluxCaptureTestExpected + { + Head = 0, + Track = 0, + SubTrack = 0, + CaptureIndex = 2, + IndexResolution = 62500, + DataResolution = 62500 + }, + new FluxCaptureTestExpected + { + Head = 1, + Track = 0, + SubTrack = 0, + CaptureIndex = 0, + IndexResolution = 62500, + DataResolution = 62500 + }, + new FluxCaptureTestExpected + { + Head = 1, + Track = 0, + SubTrack = 0, + CaptureIndex = 1, + IndexResolution = 62500, + DataResolution = 62500 + }, + new FluxCaptureTestExpected + { + Head = 1, + Track = 0, + SubTrack = 0, + CaptureIndex = 2, + IndexResolution = 62500, + DataResolution = 62500 + }, + new FluxCaptureTestExpected + { + Head = 1, + Track = 1, + SubTrack = 0, + CaptureIndex = 0, + IndexResolution = 62500, + DataResolution = 62500 + }, + new FluxCaptureTestExpected + { + Head = 0, + Track = 2, + SubTrack = 0, + CaptureIndex = 0, + IndexResolution = 62500, + DataResolution = 62500 + }, + new FluxCaptureTestExpected + { + Head = 0, + Track = 2, + SubTrack = 0, + CaptureIndex = 1, + IndexResolution = 62500, + DataResolution = 62500 + }, + new FluxCaptureTestExpected + { + Head = 0, + Track = 2, + SubTrack = 0, + CaptureIndex = 2, + IndexResolution = 62500, + DataResolution = 62500 + }, + ] + }, + ]; +} diff --git a/Aaru.Tests/Images/AaruFormat/V2Flux.cs b/Aaru.Tests/Images/AaruFormat/V2Flux.cs new file mode 100644 index 000000000..2c284e4d4 --- /dev/null +++ b/Aaru.Tests/Images/AaruFormat/V2Flux.cs @@ -0,0 +1,105 @@ +// /*************************************************************************** +// Aaru Data Preservation Suite +// ---------------------------------------------------------------------------- +// +// Filename : V2Flux.cs +// Author(s) : Rebecca Wallander +// +// Component : Aaru unit testing. +// +// --[ License ] -------------------------------------------------------------- +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2026 Rebecca Wallander +// ****************************************************************************/ + +using System.IO; +using Aaru.CommonTypes.Interfaces; +using NUnit.Framework; + +namespace Aaru.Tests.Images.AaruFormat; + +[TestFixture] +public class V2Flux : FluxMediaImageTest +{ + public override string DataFolder => Path.Combine(Consts.TestFilesRoot, "Media image formats", "AaruFormat", "V2", "Flux"); + public override IMediaImage Plugin => new Aaru.Images.AaruFormat(); + + public override FluxImageTestExpected[] Tests => + [ + new() + { + TestFile = "dos_1.25_release_a - Disk 1.aif", + FluxCaptureCount = 320, + FluxCaptures = [ + new FluxCaptureTestExpected + { + Head = 0, + Track = 0, + SubTrack = 0, + CaptureIndex = 0, + IndexResolution = 125000, + DataResolution = 125000 + }, + new FluxCaptureTestExpected + { + Head = 0, + Track = 0, + SubTrack = 0, + CaptureIndex = 1, + IndexResolution = 125000, + DataResolution = 125000 + }, + new FluxCaptureTestExpected + { + Head = 0, + Track = 0, + SubTrack = 0, + CaptureIndex = 2, + IndexResolution = 125000, + DataResolution = 125000 + }, + new FluxCaptureTestExpected + { + Head = 1, + Track = 0, + SubTrack = 0, + CaptureIndex = 0, + IndexResolution = 125000, + DataResolution = 125000 + }, + new FluxCaptureTestExpected + { + Head = 1, + Track = 0, + SubTrack = 0, + CaptureIndex = 1, + IndexResolution = 125000, + DataResolution = 125000 + }, + new FluxCaptureTestExpected + { + Head = 1, + Track = 0, + SubTrack = 0, + CaptureIndex = 2, + IndexResolution = 125000, + DataResolution = 125000 + }, + ] + } + ]; +} \ No newline at end of file diff --git a/Aaru.Tests/Images/FluxMediaImageTest.cs b/Aaru.Tests/Images/FluxMediaImageTest.cs new file mode 100644 index 000000000..af898c68c --- /dev/null +++ b/Aaru.Tests/Images/FluxMediaImageTest.cs @@ -0,0 +1,188 @@ +// /*************************************************************************** +// Aaru Data Preservation Suite +// ---------------------------------------------------------------------------- +// +// Filename : FluxMediaImageTest.cs +// Author(s) : Rebecca Wallander +// +// Component : Aaru unit testing. +// +// --[ License ] -------------------------------------------------------------- +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2026 Rebecca Wallander +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Aaru.CommonTypes; +using Aaru.CommonTypes.Enums; +using Aaru.CommonTypes.Interfaces; +using Aaru.CommonTypes.Structs; +using Aaru.Core; +using FluentAssertions; +using NUnit.Framework; + +namespace Aaru.Tests.Images; + +public abstract class FluxMediaImageTest : BaseMediaImageTest +{ + public abstract FluxImageTestExpected[] Tests { get; } + + [OneTimeSetUp] + public void InitTest() => PluginBase.Init(); + + [Test] + public void Info() + { + Environment.CurrentDirectory = DataFolder; + + Assert.Multiple(() => + { + foreach(FluxImageTestExpected test in Tests) + { + string testFile = test.TestFile; + + bool exists = File.Exists(testFile); + Assert.That(exists, string.Format(Localization._0_not_found, testFile)); + + // ReSharper disable once ConditionIsAlwaysTrueOrFalse + // It arrives here... + if(!exists) continue; + + IFilter filter = PluginRegister.Singleton.GetFilter(testFile); + filter.Open(testFile); + + var image = Activator.CreateInstance(Plugin.GetType()) as IMediaImage; + + Assert.That(image, + Is.Not.Null, + string.Format(Localization.Could_not_instantiate_filesystem_for_0, testFile)); + + ErrorNumber opened = image.Open(filter); + Assert.That(opened, Is.EqualTo(ErrorNumber.NoError), string.Format(Localization.Open_0, testFile)); + + if(opened != ErrorNumber.NoError) continue; + + if(image is not IFluxImage fluxImage) + { + Assert.Fail($"Image {testFile} does not implement IFluxImage"); + continue; + } + + ErrorNumber error = fluxImage.GetAllFluxCaptures(out List captures); + + Assert.That(error, Is.EqualTo(ErrorNumber.NoError), $"GetAllFluxCaptures failed with {error}"); + Assert.That(captures, Is.Not.Null, "GetAllFluxCaptures returned null"); + Assert.That(captures, Is.Not.Empty, "GetAllFluxCaptures returned empty list"); + + Assert.That(captures.Count, + Is.EqualTo(test.FluxCaptureCount), + $"Expected {test.FluxCaptureCount} flux captures, got {captures.Count}"); + + captures.Should().NotBeEmpty("Flux captures list should not be empty"); + + foreach(FluxCapture capture in captures) + { + // Verify each capture has valid properties + capture.IndexResolution.Should().BeGreaterThan(0, "IndexResolution should be greater than 0"); + capture.DataResolution.Should().BeGreaterThan(0, "DataResolution should be greater than 0"); + } + } + }); + } + + [Test] + public void Contents() + { + Environment.CurrentDirectory = DataFolder; + + Assert.Multiple(() => + { + foreach(FluxImageTestExpected test in Tests) + { + string testFile = test.TestFile; + + bool exists = File.Exists(testFile); + Assert.That(exists, string.Format(Localization._0_not_found, testFile)); + + // ReSharper disable once ConditionIsAlwaysTrueOrFalse + // It arrives here... + if(!exists) continue; + + IFilter filter = PluginRegister.Singleton.GetFilter(testFile); + filter.Open(testFile); + + var image = Activator.CreateInstance(Plugin.GetType()) as IMediaImage; + + Assert.That(image, + Is.Not.Null, + string.Format(Localization.Could_not_instantiate_filesystem_for_0, testFile)); + + ErrorNumber opened = image.Open(filter); + Assert.That(opened, Is.EqualTo(ErrorNumber.NoError), string.Format(Localization.Open_0, testFile)); + + if(opened != ErrorNumber.NoError) continue; + + if(image is not IFluxImage fluxImage) + { + Assert.Fail($"Image {testFile} does not implement IFluxImage"); + continue; + } + + ErrorNumber error = fluxImage.GetAllFluxCaptures(out List captures); + + Assert.That(error, Is.EqualTo(ErrorNumber.NoError), "GetAllFluxCaptures should succeed"); + Assert.That(captures, Is.Not.Null.And.Not.Empty, "Should have at least one flux capture"); + Assert.That(captures.Count, + Is.EqualTo(test.FluxCaptureCount), + $"Expected {test.FluxCaptureCount} flux captures, got {captures.Count}"); + + // If FluxCaptures array is provided, validate those captures + if(test.FluxCaptures != null && test.FluxCaptures.Length > 0) + { + Assert.That(captures.Count, + Is.GreaterThanOrEqualTo(test.FluxCaptures.Length), + $"Image has {captures.Count} captures, but {test.FluxCaptures.Length} expected captures specified"); + + foreach(FluxCaptureTestExpected expectedCapture in test.FluxCaptures) + { + FluxCapture actualCapture = captures.Find(c => c.Head == expectedCapture.Head && + c.Track == expectedCapture.Track && + c.SubTrack == expectedCapture.SubTrack && + c.CaptureIndex == expectedCapture.CaptureIndex); + + Assert.That(actualCapture, + Is.Not.Null, + $"Flux capture not found: head={expectedCapture.Head}, track={expectedCapture.Track}, subTrack={expectedCapture.SubTrack}, captureIndex={expectedCapture.CaptureIndex}"); + + if(actualCapture != null) + { + Assert.That(actualCapture.IndexResolution, + Is.EqualTo(expectedCapture.IndexResolution), + $"IndexResolution mismatch for head={expectedCapture.Head}, track={expectedCapture.Track}, subTrack={expectedCapture.SubTrack}, captureIndex={expectedCapture.CaptureIndex}"); + Assert.That(actualCapture.DataResolution, + Is.EqualTo(expectedCapture.DataResolution), + $"DataResolution mismatch for head={expectedCapture.Head}, track={expectedCapture.Track}, subTrack={expectedCapture.SubTrack}, captureIndex={expectedCapture.CaptureIndex}"); + } + } + } + } + }); + } +} diff --git a/Aaru.Tests/Images/SuperCardPro.cs b/Aaru.Tests/Images/SuperCardPro.cs new file mode 100644 index 000000000..a4a57b174 --- /dev/null +++ b/Aaru.Tests/Images/SuperCardPro.cs @@ -0,0 +1,105 @@ +// /*************************************************************************** +// Aaru Data Preservation Suite +// ---------------------------------------------------------------------------- +// +// Filename : SuperCardPro.cs +// Author(s) : Rebecca Wallander +// +// Component : Aaru unit testing. +// +// --[ License ] -------------------------------------------------------------- +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2026 Rebecca Wallander +// ****************************************************************************/ + +using System.IO; +using Aaru.CommonTypes.Interfaces; +using NUnit.Framework; + +namespace Aaru.Tests.Images; + +[TestFixture] +public class SuperCardPro : FluxMediaImageTest +{ + public override string DataFolder => Path.Combine(Consts.TestFilesRoot, "Media image formats", "SuperCardPro"); + public override IMediaImage Plugin => new Aaru.Images.SuperCardPro(); + + public override FluxImageTestExpected[] Tests => + [ + new() + { + TestFile = "Go Simulator (1992)(Infogrames).scp", + FluxCaptureCount = 160, + FluxCaptures = [ + new FluxCaptureTestExpected + { + Head = 0, + Track = 0, + SubTrack = 0, + CaptureIndex = 0, + IndexResolution = 25000, + DataResolution = 25000 + }, + new FluxCaptureTestExpected + { + Head = 1, + Track = 0, + SubTrack = 0, + CaptureIndex = 0, + IndexResolution = 25000, + DataResolution = 25000 + }, + new FluxCaptureTestExpected + { + Head = 0, + Track = 1, + SubTrack = 0, + CaptureIndex = 0, + IndexResolution = 25000, + DataResolution = 25000 + }, + new FluxCaptureTestExpected + { + Head = 1, + Track = 1, + SubTrack = 0, + CaptureIndex = 0, + IndexResolution = 25000, + DataResolution = 25000 + }, + new FluxCaptureTestExpected + { + Head = 0, + Track = 2, + SubTrack = 0, + CaptureIndex = 0, + IndexResolution = 25000, + DataResolution = 25000 + }, + new FluxCaptureTestExpected + { + Head = 1, + Track = 2, + SubTrack = 0, + CaptureIndex = 0, + IndexResolution = 25000, + DataResolution = 25000 + }, + ] + }, + ]; +} diff --git a/Aaru.Tests/Structs.cs b/Aaru.Tests/Structs.cs index 83d149c98..e3aa1eb97 100644 --- a/Aaru.Tests/Structs.cs +++ b/Aaru.Tests/Structs.cs @@ -76,6 +76,30 @@ public class TapeImageTestExpected : BlockImageTestExpected public new TapePartition[] Partitions; } +public class FluxCaptureTestExpected +{ + /// Physical head (0-based) + public uint Head; + /// Physical track (0-based) + public ushort Track; + /// Physical sub-track (0-based, e.g. half-track) + public byte SubTrack; + /// Capture index for this head/track/subTrack combination + public uint CaptureIndex; + /// Expected index resolution in picoseconds + public ulong IndexResolution; + /// Expected data resolution in picoseconds + public ulong DataResolution; +} + +public class FluxImageTestExpected : BlockImageTestExpected +{ + /// Expected number of flux captures in the image + public uint FluxCaptureCount; + /// Expected flux captures to validate + public FluxCaptureTestExpected[] FluxCaptures; +} + public class PartitionTest { public Partition[] Partitions; From e6effdef903a14ff6733635510255f505ed56461 Mon Sep 17 00:00:00 2001 From: Rebecca Wallander Date: Thu, 1 Jan 2026 13:39:03 +0100 Subject: [PATCH 4/5] Update enum value --- Aaru.Images/AaruFormat/Enums.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Aaru.Images/AaruFormat/Enums.cs b/Aaru.Images/AaruFormat/Enums.cs index 555443a04..a8d8223c7 100644 --- a/Aaru.Images/AaruFormat/Enums.cs +++ b/Aaru.Images/AaruFormat/Enums.cs @@ -217,7 +217,7 @@ public sealed partial class AaruFormat /// Requested flux data not present in image. /// <remarks>AARUF_ERROR_FLUX_DATA_NOT_FOUND</remarks> /// - FluxDataNotFound = -31 + FluxDataNotFound = -32 } #endregion From 8994b99b105a052d0c8af914bcfd7f52f82b2f44 Mon Sep 17 00:00:00 2001 From: Rebecca Wallander Date: Sun, 4 Jan 2026 10:07:15 +0100 Subject: [PATCH 5/5] Fix missing comma in Enums.cs --- Aaru.Images/AaruFormat/Enums.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Aaru.Images/AaruFormat/Enums.cs b/Aaru.Images/AaruFormat/Enums.cs index 96c7ea9f6..785952a52 100644 --- a/Aaru.Images/AaruFormat/Enums.cs +++ b/Aaru.Images/AaruFormat/Enums.cs @@ -216,7 +216,7 @@ public sealed partial class AaruFormat /// Sector length is too big. /// <remarks>AARUF_ERROR_INVALID_SECTOR_LENGTH</remarks> /// - InvalidSectorLength = -31 + InvalidSectorLength = -31, /// /// Requested flux data not present in image. /// <remarks>AARUF_ERROR_FLUX_DATA_NOT_FOUND</remarks> @@ -225,4 +225,4 @@ public sealed partial class AaruFormat } #endregion -} \ No newline at end of file +}