Merge remote-tracking branch 'origin/fakeshemp/flux' into devel

This commit is contained in:
2026-01-04 16:31:51 +00:00
22 changed files with 1342 additions and 39 deletions

View File

@@ -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
/// <param name="track">Physical track (position of the heads over the floppy media, 0-based)</param>
/// <param name="length">The number of captures</param>
ErrorNumber SubTrackLength(uint head, ushort track, out byte length);
/// <summary>
/// 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.
/// </summary>
/// <param name="captures">List of all flux captures in the image, or null if an error occurred</param>
/// <returns>Error number</returns>
ErrorNumber GetAllFluxCaptures(out List<FluxCapture> captures);
}

View File

@@ -257,4 +257,14 @@ public class LinearMemoryInterleave
/// <summary>How many bytes in memory to skip every device byte</summary>
public uint Value { get; set; }
}
public class FluxCapture
{
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; }
}

View File

@@ -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<FluxCapture> 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;

View File

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

View File

@@ -0,0 +1,44 @@
// /***************************************************************************
// Aaru Data Preservation Suite
// ----------------------------------------------------------------------------
//
// Filename : FluxCaptureModel.cs
// Author(s) : Rebecca Wallander <sakcheen@gmail.com>
//
// 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 <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------
// 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; }
}

View File

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

View File

@@ -0,0 +1,66 @@
// /***************************************************************************
// Aaru Data Preservation Suite
// ----------------------------------------------------------------------------
//
// Filename : FluxInfoViewModel.cs
// Author(s) : Rebecca Wallander <sakcheen@gmail.com>
//
// 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 <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------
// 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<FluxCapture> 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<FluxCaptureModel> FluxCaptures { get; }
}

View File

@@ -734,6 +734,13 @@
<ContentControl Content="{Binding DvdInfo, Mode=OneWay}"
Margin="8" />
</TabItem>
<TabItem IsVisible="{Binding !!FluxInfo, Mode=OneWay}">
<TabItem.Header>
<controls:SpectreTextBlock Text="Flux Captures" />
</TabItem.Header>
<ContentControl Content="{Binding FluxInfo, Mode=OneWay}"
Margin="8" />
</TabItem>
<TabItem IsVisible="{Binding !!DvdWritableInfo, Mode=OneWay}">
<TabItem.Header>
<StackPanel Orientation="Horizontal"

View File

@@ -0,0 +1,156 @@
<!--
// /***************************************************************************
// Aaru Data Preservation Suite
//
//
// Filename : FluxInfo.xaml
// Author(s) : Rebecca Wallander <sakcheen@gmail.com>
//
// 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 <http://www.gnu.org/licenses/>.
//
//
// Copyright © 2011-2025 Rebecca Wallander
// ****************************************************************************/
-->
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:tabs="clr-namespace:Aaru.Gui.ViewModels.Tabs"
xmlns:localization="clr-namespace:Aaru.Localization;assembly=Aaru.Localization"
xmlns:controls="clr-namespace:Aaru.Gui.Controls"
mc:Ignorable="d"
d:DesignWidth="800"
d:DesignHeight="450"
x:DataType="tabs:FluxInfoViewModel"
x:Class="Aaru.Gui.Views.Tabs.FluxInfo">
<Design.DataContext>
<tabs:FluxInfoViewModel />
</Design.DataContext>
<Grid Margin="8">
<DataGrid ItemsSource="{Binding FluxCaptures, Mode=OneWay}"
IsReadOnly="True"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTemplateColumn Width="*">
<DataGridTemplateColumn.HeaderTemplate>
<DataTemplate>
<controls:SpectreTextBlock Text="Head"
Padding="5"
VerticalAlignment="Center" />
</DataTemplate>
</DataGridTemplateColumn.HeaderTemplate>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Head, Mode=OneWay}"
Padding="5"
VerticalAlignment="Center" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Width="*">
<DataGridTemplateColumn.HeaderTemplate>
<DataTemplate>
<controls:SpectreTextBlock Text="{x:Static localization:Core.Title_Track}"
Padding="5"
VerticalAlignment="Center" />
</DataTemplate>
</DataGridTemplateColumn.HeaderTemplate>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Track, Mode=OneWay}"
Padding="5"
VerticalAlignment="Center" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Width="*">
<DataGridTemplateColumn.HeaderTemplate>
<DataTemplate>
<controls:SpectreTextBlock Text="SubTrack"
Padding="5"
VerticalAlignment="Center" />
</DataTemplate>
</DataGridTemplateColumn.HeaderTemplate>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding SubTrack, Mode=OneWay}"
Padding="5"
VerticalAlignment="Center" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Width="*">
<DataGridTemplateColumn.HeaderTemplate>
<DataTemplate>
<controls:SpectreTextBlock Text="Capture Index"
Padding="5"
VerticalAlignment="Center" />
</DataTemplate>
</DataGridTemplateColumn.HeaderTemplate>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding CaptureIndex, Mode=OneWay}"
Padding="5"
VerticalAlignment="Center" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Width="*">
<DataGridTemplateColumn.HeaderTemplate>
<DataTemplate>
<controls:SpectreTextBlock Text="Index Resolution"
Padding="5"
VerticalAlignment="Center" />
</DataTemplate>
</DataGridTemplateColumn.HeaderTemplate>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding IndexResolution, Mode=OneWay}"
Padding="5"
VerticalAlignment="Center" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Width="*">
<DataGridTemplateColumn.HeaderTemplate>
<DataTemplate>
<controls:SpectreTextBlock Text="Data Resolution"
Padding="5"
VerticalAlignment="Center" />
</DataTemplate>
</DataGridTemplateColumn.HeaderTemplate>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding DataResolution, Mode=OneWay}"
Padding="5"
VerticalAlignment="Center" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</UserControl>

View File

@@ -0,0 +1,44 @@
// /***************************************************************************
// Aaru Data Preservation Suite
// ----------------------------------------------------------------------------
//
// Filename : FluxInfo.xaml.cs
// Author(s) : Rebecca Wallander <sakcheen@gmail.com>
//
// 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 <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------
// 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);
}

View File

@@ -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];
}
/// <inheritdoc />
public ErrorNumber GetAllFluxCaptures(out List<FluxCapture> 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;
}
}

View File

@@ -6,7 +6,7 @@ namespace Aaru.Images;
/// <inheritdoc cref="Aaru.CommonTypes.Interfaces.IWritableOpticalImage" />
/// <summary>Implements reading and writing AaruFormat media images</summary>
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;

View File

@@ -40,7 +40,9 @@ public sealed partial class AaruFormat
/// <summary>Block containing list of indexes for Compact Disc tracks</summary>
CompactDiscIndexesBlock = 0x58494443,
/// <summary>Block containing JSON version of Aaru Metadata</summary>
AaruMetadataJsonBlock = 0x444D534A
AaruMetadataJsonBlock = 0x444D534A,
/// <summary>Block containing list of flux captures</summary>
FluxCapturesBlock = 0x58554C46
}
#endregion
@@ -211,12 +213,16 @@ public sealed partial class AaruFormat
/// &lt;remarks&gt;AARUF_ERROR_METADATA_NOT_PRESENT&lt;/remarks&gt;
/// </summary>
MetadataNotPresent = -30,
/// <summary>
/// Sector length is too big.
/// &lt;remarks&gt;AARUF_ERROR_INVALID_SECTOR_LENGTH&lt;/remarks&gt;
/// </summary>
InvalidSectorLength = -31
InvalidSectorLength = -31,
/// <summary>
/// Requested flux data not present in image.
/// &lt;remarks&gt;AARUF_ERROR_FLUX_DATA_NOT_FOUND&lt;/remarks&gt;
/// </summary>
FluxDataNotFound = -32
}
#endregion
}
}

View File

@@ -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<FluxCapture> _fluxCaptures;
#region IWritableFluxImage Members
/// <inheritdoc />
public List<FluxCapture> 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 [];
}
byte[] buffer = new byte[length];
res = aaruf_get_flux_captures(_context, buffer, ref length);
if(res != Status.Ok)
{
ErrorMessage = StatusToErrorMessage(res);
return [];
}
int fluxCaptureSize = Marshal.SizeOf<FluxCaptureEntry>();
int fluxCaptureCount = (int)length / fluxCaptureSize;
_fluxCaptures = new List<FluxCapture>(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<FluxCaptureEntry>(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;
}
}
/// <inheritdoc />
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;
}
/// <inheritdoc />
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;
}
/// <inheritdoc />
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;
}
/// <inheritdoc />
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;
}
/// <inheritdoc />
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;
}
/// <inheritdoc />
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 _);
}
/// <inheritdoc />
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);
}
/// <inheritdoc />
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;
}
/// <inheritdoc />
public ErrorNumber GetAllFluxCaptures(out List<FluxCapture> captures)
{
captures = FluxCaptures;
return ErrorNumber.NoError;
}
/// <inheritdoc />
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;
}
/// <inheritdoc />
public ErrorNumber WriteFluxIndexCapture(ulong resolution, byte[] index, uint head, ushort track, byte subTrack, uint captureIndex)
{
return ErrorNumber.NotImplemented;
}
/// <inheritdoc />
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);
}

View File

@@ -94,6 +94,7 @@ public sealed partial class AaruFormat
Status.TapePartitionNotFound => "Requested tape partition is not present in image.",
Status.MetadataNotPresent => "Requested metadata is not present in image.",
Status.InvalidSectorLength => "Requested sector length is too big.",
Status.FluxDataNotFound => "Requested flux data is not present in image.",
_ => "Unknown error occurred."
};
}

View File

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

View File

@@ -33,9 +33,11 @@
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.Helpers;
using Aaru.Logging;
@@ -554,6 +556,39 @@ public sealed partial class SuperCardPro
return indexCapture;
}
/// <inheritdoc />
public ErrorNumber GetAllFluxCaptures(out List<FluxCapture> captures)
{
captures = [];
if(ScpTracks is { Count: > 0 })
{
ulong resolution = (ulong)((Header.resolution + 1) * DEFAULT_RESOLUTION);
captures = [.. ScpTracks.Select(kvp =>
{
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
return new FluxCapture
{
Head = head,
Track = track,
SubTrack = subTrack,
CaptureIndex = 0, // SuperCardPro always has one capture per track
IndexResolution = resolution,
DataResolution = resolution
};
})];
}
return ErrorNumber.NoError;
}
/// <inheritdoc />
public ErrorNumber ReadMediaTag(MediaTagType tag, out byte[] buffer)
{

202
Aaru.Tests/Images/A2R.cs Normal file
View File

@@ -0,0 +1,202 @@
// /***************************************************************************
// Aaru Data Preservation Suite
// ----------------------------------------------------------------------------
//
// Filename : A2R.cs
// Author(s) : Rebecca Wallander <sakcheen+github@gmail.com>
//
// 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 <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------
// 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
},
]
},
];
}

View File

@@ -0,0 +1,105 @@
// /***************************************************************************
// Aaru Data Preservation Suite
// ----------------------------------------------------------------------------
//
// Filename : V2Flux.cs
// Author(s) : Rebecca Wallander <sakcheen+github@gmail.com>
//
// 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 <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------
// 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
},
]
}
];
}

View File

@@ -0,0 +1,188 @@
// /***************************************************************************
// Aaru Data Preservation Suite
// ----------------------------------------------------------------------------
//
// Filename : FluxMediaImageTest.cs
// Author(s) : Rebecca Wallander <sakcheen+github@gmail.com>
//
// 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 <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------
// 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<FluxCapture> 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<FluxCapture> 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}");
}
}
}
}
});
}
}

View File

@@ -0,0 +1,105 @@
// /***************************************************************************
// Aaru Data Preservation Suite
// ----------------------------------------------------------------------------
//
// Filename : SuperCardPro.cs
// Author(s) : Rebecca Wallander <sakcheen+github@gmail.com>
//
// 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 <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------
// 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
},
]
},
];
}

View File

@@ -76,6 +76,30 @@ public class TapeImageTestExpected : BlockImageTestExpected
public new TapePartition[] Partitions;
}
public class FluxCaptureTestExpected
{
/// <summary>Physical head (0-based)</summary>
public uint Head;
/// <summary>Physical track (0-based)</summary>
public ushort Track;
/// <summary>Physical sub-track (0-based, e.g. half-track)</summary>
public byte SubTrack;
/// <summary>Capture index for this head/track/subTrack combination</summary>
public uint CaptureIndex;
/// <summary>Expected index resolution in picoseconds</summary>
public ulong IndexResolution;
/// <summary>Expected data resolution in picoseconds</summary>
public ulong DataResolution;
}
public class FluxImageTestExpected : BlockImageTestExpected
{
/// <summary>Expected number of flux captures in the image</summary>
public uint FluxCaptureCount;
/// <summary>Expected flux captures to validate</summary>
public FluxCaptureTestExpected[] FluxCaptures;
}
public class PartitionTest
{
public Partition[] Partitions;