diff --git a/Aaru.CommonTypes b/Aaru.CommonTypes
index a5fe3e6d9..69b26f6cd 160000
--- a/Aaru.CommonTypes
+++ b/Aaru.CommonTypes
@@ -1 +1 @@
-Subproject commit a5fe3e6d91240c6287e5b43f512fe1c898b7fec4
+Subproject commit 69b26f6cd54fad1e3e5d9f9facbbd3db781bde77
diff --git a/Aaru.Core/ImageInfo.cs b/Aaru.Core/ImageInfo.cs
index c59b78dbd..1124dde25 100644
--- a/Aaru.Core/ImageInfo.cs
+++ b/Aaru.Core/ImageInfo.cs
@@ -805,6 +805,9 @@ public static class ImageInfo
}
}
+ if(imageFormat is IFluxImage fluxImage)
+ AaruConsole.WriteLine(Localization.Core.Image_flux_captures);
+
if(imageFormat is not IOpticalMediaImage opticalImage)
return;
diff --git a/Aaru.Images/SuperCardPro/Constants.cs b/Aaru.Images/SuperCardPro/Constants.cs
index 57c223e09..852b9e319 100644
--- a/Aaru.Images/SuperCardPro/Constants.cs
+++ b/Aaru.Images/SuperCardPro/Constants.cs
@@ -46,4 +46,11 @@ public sealed partial class SuperCardPro
{
0x54, 0x52, 0x4B
};
+
+ /// SuperCardPro device default capture resolution: 25 nanoseconds.
+ const ushort DEFAULT_RESOLUTION = 25000;
+ /// SuperCardPro format full (with track offsets) header length.
+ const uint FULL_HEADER_OFFSET = 0x2b0;
+ /// SuperCardPro format header length (without track offsets).
+ const byte HEADER_OFFSET = 0x10;
}
\ No newline at end of file
diff --git a/Aaru.Images/SuperCardPro/Enums.cs b/Aaru.Images/SuperCardPro/Enums.cs
index 24be535c1..d9419bf97 100644
--- a/Aaru.Images/SuperCardPro/Enums.cs
+++ b/Aaru.Images/SuperCardPro/Enums.cs
@@ -40,30 +40,37 @@ public sealed partial class SuperCardPro
[SuppressMessage("ReSharper", "InconsistentNaming")]
public enum ScpDiskType : byte
{
- Commodore64 = 0x00, CommodoreAmiga = 0x04, AtariFMSS = 0x10,
- AtariFMDS = 0x11, AtariFSEx = 0x12, AtariSTSS = 0x14,
- AtariSTDS = 0x15, AppleII = 0x20, AppleIIPro = 0x21,
- Apple400K = 0x24, Apple800K = 0x25, Apple144 = 0x26,
- PC360K = 0x30, PC720K = 0x31, PC12M = 0x32,
- PC144M = 0x33, TandySSSD = 0x40, TandySSDD = 0x41,
- TandyDSSD = 0x42, TandyDSDD = 0x43, Ti994A = 0x50,
- RolandD20 = 0x60
+ Commodore64 = 0x00, CommodoreAmiga = 0x04, CommodoreAmigaHD = 0x08,
+ AtariFMSS = 0x10, AtariFMDS = 0x11, AtariFMEx = 0x12,
+ AtariSTSS = 0x14, AtariSTDS = 0x15, AppleII = 0x20,
+ AppleIIPro = 0x21, Apple400K = 0x24, Apple800K = 0x25,
+ Apple144 = 0x26, PC360K = 0x30, PC720K = 0x31,
+ PC12M = 0x32, PC144M = 0x33, TandySSSD = 0x40,
+ TandySSDD = 0x41, TandyDSSD = 0x42, TandyDSDD = 0x43,
+ Ti994A = 0x50, RolandD20 = 0x60, AmstradCPC = 0x70,
+ Generic360K = 0x80, Generic12M = 0x81, Generic720K = 0x84,
+ Generic144M = 0x85, TapeGCR1 = 0xE0, TapeGCR2 = 0xE1,
+ TapeMFM = 0xE2, HddMFM = 0xF0, HddRLL = 0xF1
}
[Flags]
public enum ScpFlags : byte
{
- /// If set flux starts at index pulse
- Index = 0x00,
- /// If set drive is 96tpi
+ /// If set, flux starts at index pulse
+ StartsAtIndex = 0x00,
+ /// If set, drive is 96tpi, else 48tpi
Tpi = 0x02,
- /// If set drive is 360rpm
+ /// If set, drive is 360rpm, else 300rpm
Rpm = 0x04,
- /// If set image contains normalized data
+ /// If set, image contains normalized data
Normalized = 0x08,
- /// If set image is read/write capable
+ /// If set, image is read/write capable
Writable = 0x10,
/// If set, image has footer
- HasFooter = 0x20
+ HasFooter = 0x20,
+ /// If set, image is not floppy
+ NotFloppy = 0x40,
+ /// If set, image was not generated by SuperCard Pro device
+ CreatedByOtherDevice = 0x80
}
}
\ No newline at end of file
diff --git a/Aaru.Images/SuperCardPro/Helpers.cs b/Aaru.Images/SuperCardPro/Helpers.cs
index 9586467bf..fcd2353ee 100644
--- a/Aaru.Images/SuperCardPro/Helpers.cs
+++ b/Aaru.Images/SuperCardPro/Helpers.cs
@@ -31,6 +31,7 @@
// ****************************************************************************/
using System;
+using System.Collections.Generic;
using System.IO;
using System.Text;
using Aaru.Helpers;
@@ -58,4 +59,113 @@ public sealed partial class SuperCardPro
return Encoding.UTF8.GetString(str);
}
+
+ ///
+ /// Takes a Head, Track and Sub-Track representation and converts it to the Track representation used by SCP.
+ ///
+ /// The head number
+ /// The track number
+ /// The sub-track number
+ /// SCP format track number
+ static long HeadTrackSubToScpTrack(uint head, ushort track, byte subTrack) =>
+
+ // TODO: Support single-sided disks
+ head + (track * 2);
+
+ static byte[] UInt32ToFluxRepresentation(uint ticks)
+ {
+ uint over = ticks / 255;
+
+ if(over == 0)
+ return new[]
+ {
+ (byte)ticks
+ };
+
+ byte[] expanded = new byte[over + 1];
+
+ Array.Fill(expanded, (byte)255, 0, (int)over);
+ expanded[^1] = (byte)(ticks % 255);
+
+ return expanded;
+ }
+
+ static byte[] UInt16ToFluxRepresentation(ushort ticks) => UInt32ToFluxRepresentation(ticks);
+
+ static List FluxRepresentationsToUInt32List(IEnumerable flux)
+ {
+ List scpData = new();
+ uint tick = 0;
+
+ foreach(byte b in flux)
+ if(b == 255)
+ tick += 255;
+ else
+ {
+ tick += b;
+ scpData.Add(tick);
+ tick = 0;
+ }
+
+ return scpData;
+ }
+
+ static List FluxRepresentationsToUInt16List(IEnumerable flux, IReadOnlyList indices,
+ out uint[] trackLengths)
+ {
+ List scpData = new();
+ ushort tick = 0;
+
+ List revolutionLength = new();
+ uint revolutionTicks = 0;
+ uint revolutionCells = 0;
+ ushort index = 0;
+
+ foreach(byte b in flux)
+ {
+ if(b == 255)
+ {
+ tick += 255;
+ revolutionTicks += 255;
+ }
+ else
+ {
+ tick += b;
+ scpData.AddRange(BigEndianBitConverter.GetBytes(tick));
+ tick = 0;
+
+ revolutionTicks += b;
+
+ if(revolutionTicks > indices[index] - 1)
+ {
+ revolutionLength.Add(revolutionCells);
+ revolutionTicks = 0;
+ revolutionCells = 0;
+ index++;
+ }
+
+ revolutionCells++;
+ }
+ }
+
+ revolutionLength.Add(revolutionCells);
+
+ trackLengths = revolutionLength.ToArray();
+
+ return scpData;
+ }
+
+ static uint CalculateChecksum(Stream stream)
+ {
+ byte[] wholeFile = new byte[stream.Length];
+ uint sum = 0;
+
+ stream.Position = 0;
+ stream.EnsureRead(wholeFile, 0, wholeFile.Length);
+
+ for(int i = HEADER_OFFSET; i < wholeFile.Length; i++)
+ sum += wholeFile[i];
+
+ return sum;
+ }
}
\ No newline at end of file
diff --git a/Aaru.Images/SuperCardPro/Properties.cs b/Aaru.Images/SuperCardPro/Properties.cs
index 50b07eee4..ebf8654d5 100644
--- a/Aaru.Images/SuperCardPro/Properties.cs
+++ b/Aaru.Images/SuperCardPro/Properties.cs
@@ -32,7 +32,9 @@
using System;
using System.Collections.Generic;
+using Aaru.CommonTypes;
using Aaru.CommonTypes.AaruMetadata;
+using Aaru.CommonTypes.Enums;
using Aaru.CommonTypes.Structs;
namespace Aaru.DiscImages;
@@ -53,4 +55,37 @@ public sealed partial class SuperCardPro
public List DumpHardware => null;
///
public Metadata AaruMetadata => null;
+ ///
+ public IEnumerable SupportedMediaTags => null;
+ ///
+ public IEnumerable SupportedMediaTypes => new[]
+ {
+ // TODO: SCP supports a lot more formats, please add more whence tested.
+ MediaType.DOS_35_DS_DD_9, MediaType.DOS_35_HD, MediaType.DOS_525_DS_DD_9, MediaType.DOS_525_HD,
+ MediaType.Unknown
+ };
+ ///
+ public IEnumerable SupportedSectorTags => Array.Empty();
+ ///
+ public IEnumerable<(string name, Type type, string description, object @default)> SupportedOptions =>
+ Array.Empty<(string name, Type type, string description, object @default)>();
+ ///
+ public IEnumerable KnownExtensions => new[]
+ {
+ ".scp"
+ };
+ ///
+ public bool IsWriting { get; private set; }
+ ///
+ public string ErrorMessage { get; private set; }
+ ///
+ /// SCP can only have one resolution. This is to help avoid changing the resolution and therefore create broken
+ /// SCP files.
+ ///
+ bool IsResolutionSet { get; set; }
+ ///
+ /// SCP can only have the same amount of revolutions for all tracks. This is to help avoid changing the number of
+ /// revolutions and therefore create broken SCP files.
+ ///
+ bool IsRevolutionsSet { get; set; }
}
\ No newline at end of file
diff --git a/Aaru.Images/SuperCardPro/Read.cs b/Aaru.Images/SuperCardPro/Read.cs
index 9e953e889..2102106d2 100644
--- a/Aaru.Images/SuperCardPro/Read.cs
+++ b/Aaru.Images/SuperCardPro/Read.cs
@@ -34,6 +34,7 @@ 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.Console;
@@ -50,6 +51,8 @@ public sealed partial class SuperCardPro
_scpStream = imageFilter.GetDataForkStream();
_scpStream.Seek(0, SeekOrigin.Begin);
+ _scpFilter = imageFilter;
+
if(_scpStream.Length < Marshal.SizeOf())
return ErrorNumber.InvalidArgument;
@@ -68,12 +71,35 @@ public sealed partial class SuperCardPro
AaruConsole.DebugWriteLine("SuperCardPro plugin", "header.revolutions = {0}", Header.revolutions);
AaruConsole.DebugWriteLine("SuperCardPro plugin", "header.start = {0}", Header.start);
AaruConsole.DebugWriteLine("SuperCardPro plugin", "header.end = {0}", Header.end);
- AaruConsole.DebugWriteLine("SuperCardPro plugin", "header.flags = {0}", Header.flags);
AaruConsole.DebugWriteLine("SuperCardPro plugin", "header.bitCellEncoding = {0}", Header.bitCellEncoding);
AaruConsole.DebugWriteLine("SuperCardPro plugin", "header.heads = {0}", Header.heads);
- AaruConsole.DebugWriteLine("SuperCardPro plugin", "header.reserved = {0}", Header.reserved);
+ AaruConsole.DebugWriteLine("SuperCardPro plugin", "header.resolution = {0}", Header.resolution);
AaruConsole.DebugWriteLine("SuperCardPro plugin", "header.checksum = 0x{0:X8}", Header.checksum);
+ AaruConsole.DebugWriteLine("SuperCardPro plugin", "header.flags.StartsAtIndex = {0}",
+ Header.flags == ScpFlags.StartsAtIndex);
+
+ AaruConsole.DebugWriteLine("SuperCardPro plugin", "header.flags.Tpi = {0}",
+ Header.flags == ScpFlags.Tpi ? "96tpi" : "48tpi");
+
+ AaruConsole.DebugWriteLine("SuperCardPro plugin", "header.flags.Rpm = {0}",
+ Header.flags == ScpFlags.Rpm ? "360rpm" : "300rpm");
+
+ AaruConsole.DebugWriteLine("SuperCardPro plugin", "header.flags.Normalized = {0}",
+ Header.flags == ScpFlags.Normalized);
+
+ AaruConsole.DebugWriteLine("SuperCardPro plugin", "header.flags.Writable = {0}",
+ Header.flags == ScpFlags.Writable);
+
+ AaruConsole.DebugWriteLine("SuperCardPro plugin", "header.flags.HasFooter = {0}",
+ Header.flags == ScpFlags.HasFooter);
+
+ AaruConsole.DebugWriteLine("SuperCardPro plugin", "header.flags.NotFloppy = {0}",
+ Header.flags == ScpFlags.NotFloppy);
+
+ AaruConsole.DebugWriteLine("SuperCardPro plugin", "header.flags.CreatedByOtherDevice = {0}",
+ Header.flags == ScpFlags.CreatedByOtherDevice);
+
if(!_scpSignature.SequenceEqual(Header.signature))
return ErrorNumber.InvalidArgument;
@@ -128,6 +154,161 @@ public sealed partial class SuperCardPro
ScpTracks.Add(t, trk);
}
+ _imageInfo.MetadataMediaType = MetadataMediaType.BlockMedia;
+
+ switch(Header.type)
+ {
+ case ScpDiskType.Commodore64:
+ _imageInfo.MediaType = MediaType.CBM_1540;
+ _imageInfo.Cylinders = (uint)int.Max(((Header.end + 1) / 2), 40);
+ _imageInfo.Heads = 1;
+
+ break;
+ case ScpDiskType.CommodoreAmiga:
+ _imageInfo.MediaType = MediaType.CBM_AMIGA_35_DD;
+ _imageInfo.Cylinders = (uint)int.Max(((Header.end + 1) / 2), 80);
+ _imageInfo.Heads = 2;
+
+ break;
+ case ScpDiskType.CommodoreAmigaHD:
+ _imageInfo.MediaType = MediaType.CBM_AMIGA_35_HD;
+ _imageInfo.Cylinders = (uint)int.Max(((Header.end + 1) / 2), 80);
+ _imageInfo.Heads = 2;
+
+ break;
+ case ScpDiskType.AtariFMSS:
+ _imageInfo.MediaType = MediaType.ATARI_525_SD;
+ _imageInfo.Cylinders = (uint)int.Max(((Header.end + 1) / 2), 40);
+ _imageInfo.Heads = 1;
+
+ break;
+ case ScpDiskType.AtariFMDS:
+ _imageInfo.MediaType = MediaType.Unknown;
+ _imageInfo.Cylinders = (uint)int.Max(((Header.end + 1) / 2), 40);
+ _imageInfo.Heads = 2;
+
+ break;
+ case ScpDiskType.AtariFMEx: break;
+ case ScpDiskType.AtariSTSS:
+ _imageInfo.MediaType = MediaType.ATARI_35_SS_DD;
+ _imageInfo.Cylinders = (uint)int.Max(((Header.end + 1) / 2), 80);
+ _imageInfo.Heads = 1;
+
+ break;
+ case ScpDiskType.AtariSTDS:
+ _imageInfo.MediaType = MediaType.ATARI_35_DS_DD;
+ _imageInfo.Cylinders = (uint)int.Max(((Header.end + 1) / 2), 80);
+ _imageInfo.Heads = 2;
+
+ break;
+ case ScpDiskType.AppleII:
+ _imageInfo.MediaType = MediaType.Apple32DS;
+ _imageInfo.Cylinders = (uint)int.Max(((Header.end + 1) / 2), 40);
+ _imageInfo.Heads = 2;
+
+ break;
+ case ScpDiskType.AppleIIPro: break;
+ case ScpDiskType.Apple400K:
+ _imageInfo.MediaType = MediaType.AppleSonySS;
+ _imageInfo.Cylinders = (uint)int.Max(((Header.end + 1) / 2), 80);
+ _imageInfo.Heads = 1;
+ _imageInfo.SectorsPerTrack = 10;
+
+ break;
+ case ScpDiskType.Apple800K:
+ _imageInfo.MediaType = MediaType.AppleSonyDS;
+ _imageInfo.Cylinders = (uint)int.Max(((Header.end + 1) / 2), 80);
+ _imageInfo.Heads = 2;
+ _imageInfo.SectorsPerTrack = 10;
+
+ break;
+ case ScpDiskType.Apple144:
+ _imageInfo.MediaType = MediaType.DOS_525_HD;
+ _imageInfo.Cylinders = (uint)int.Max(((Header.end + 1) / 2), 80);
+ _imageInfo.Heads = 2;
+ _imageInfo.SectorsPerTrack = 18;
+
+ break;
+ case ScpDiskType.PC360K:
+ _imageInfo.MediaType = MediaType.DOS_525_DS_DD_9;
+ _imageInfo.Cylinders = (uint)int.Max(((Header.end + 1) / 2), 40);
+ _imageInfo.Heads = 2;
+ _imageInfo.SectorsPerTrack = 9;
+
+ break;
+ case ScpDiskType.PC720K:
+ _imageInfo.MediaType = MediaType.DOS_35_DS_DD_9;
+ _imageInfo.Cylinders = (uint)int.Max(((Header.end + 1) / 2), 80);
+ _imageInfo.Heads = 2;
+
+ break;
+ case ScpDiskType.PC12M:
+ _imageInfo.MediaType = MediaType.DOS_525_HD;
+ _imageInfo.Cylinders = (uint)int.Max(((Header.end + 1) / 2), 80);
+ _imageInfo.Heads = 2;
+
+ break;
+ case ScpDiskType.PC144M:
+ _imageInfo.MediaType = MediaType.DOS_35_HD;
+ _imageInfo.Cylinders = (uint)int.Max(((Header.end + 1) / 2), 80);
+ _imageInfo.Heads = 2;
+ _imageInfo.SectorsPerTrack = 18;
+
+ break;
+ case ScpDiskType.TandySSSD: break;
+ case ScpDiskType.TandySSDD: break;
+ case ScpDiskType.TandyDSSD: break;
+ case ScpDiskType.TandyDSDD: break;
+ case ScpDiskType.Ti994A: break;
+ case ScpDiskType.RolandD20: break;
+ case ScpDiskType.AmstradCPC: break;
+ case ScpDiskType.Generic360K:
+ _imageInfo.MediaType = MediaType.DOS_525_DS_DD_9;
+ _imageInfo.Cylinders = (uint)int.Max(((Header.end + 1) / 2), 40);
+ _imageInfo.Heads = 2;
+
+ break;
+ case ScpDiskType.Generic12M:
+ _imageInfo.MediaType = MediaType.DOS_525_HD;
+ _imageInfo.Cylinders = (uint)int.Max(((Header.end + 1) / 2), 80);
+ _imageInfo.Heads = 2;
+
+ break;
+
+ case ScpDiskType.Generic720K:
+ _imageInfo.MediaType = MediaType.DOS_35_DS_DD_9;
+ _imageInfo.Cylinders = (uint)int.Max(((Header.end + 1) / 2), 80);
+ _imageInfo.Heads = 2;
+
+ break;
+ case ScpDiskType.Generic144M:
+ _imageInfo.MediaType = MediaType.DOS_35_HD;
+ _imageInfo.Cylinders = (uint)int.Max(((Header.end + 1) / 2), 80);
+ _imageInfo.Heads = 2;
+
+ break;
+ case ScpDiskType.TapeGCR1:
+ case ScpDiskType.TapeGCR2:
+ case ScpDiskType.TapeMFM:
+ _imageInfo.MediaType = MediaType.UnknownTape;
+
+ break;
+ case ScpDiskType.HddMFM:
+ case ScpDiskType.HddRLL:
+ _imageInfo.MediaType = MediaType.GENERIC_HDD;
+
+ break;
+ default:
+ _imageInfo.MediaType = MediaType.Unknown;
+
+ _imageInfo.Cylinders =
+ (uint)int.Max((Header.end + 1) / 2, Header.flags.HasFlag(ScpFlags.Tpi) ? 80 : 40);
+
+ _imageInfo.Heads = Header.heads == 0 ? 2 : (uint)1;
+
+ break;
+ }
+
if(Header.flags.HasFlag(ScpFlags.HasFooter))
{
long position = _scpStream.Position;
@@ -245,16 +426,14 @@ public sealed partial class SuperCardPro
}
else
{
- _imageInfo.Application = "SuperCardPro";
- _imageInfo.ApplicationVersion = $"{(Header.version & 0xF0) >> 4}.{Header.version & 0xF}";
- _imageInfo.CreationTime = imageFilter.CreationTime;
+ _imageInfo.Application = (Header.flags & ScpFlags.CreatedByOtherDevice) == 0 ? "SuperCardPro" : null;
+ _imageInfo.ApplicationVersion = $"{(Header.version & 0xF0) >> 4}.{Header.version & 0xF}";
+ _imageInfo.CreationTime = imageFilter.CreationTime;
_imageInfo.LastModificationTime = imageFilter.LastWriteTime;
- _imageInfo.Version = "1.5";
+ _imageInfo.Version = "2.4";
}
- AaruConsole.ErrorWriteLine(Localization.Flux_decoding_is_not_yet_implemented);
-
- return ErrorNumber.NotImplemented;
+ return ErrorNumber.NoError;
}
///
@@ -299,4 +478,135 @@ public sealed partial class SuperCardPro
return ErrorNumber.NotSupported;
}
+
+ public ErrorNumber SubTrackLength(uint head, ushort track, out byte length)
+ {
+ length = 1;
+
+ return ErrorNumber.NoError;
+ }
+
+ public ErrorNumber CapturesLength(uint head, ushort track, byte subTrack, out uint length)
+ {
+ length = 1;
+
+ return ErrorNumber.NoError;
+ }
+
+ public ErrorNumber ReadFluxIndexResolution(uint head, ushort track, byte subTrack, uint captureIndex,
+ out ulong resolution)
+ {
+ resolution = (ulong)((Header.resolution + 1) * DEFAULT_RESOLUTION);
+
+ return ErrorNumber.NoError;
+ }
+
+ public ErrorNumber ReadFluxDataResolution(uint head, ushort track, byte subTrack, uint captureIndex,
+ out ulong resolution)
+ {
+ resolution = (ulong)((Header.resolution + 1) * DEFAULT_RESOLUTION);
+
+ return ErrorNumber.NoError;
+ }
+
+ public ErrorNumber ReadFluxResolution(uint head, ushort track, byte subTrack, uint captureIndex,
+ out ulong indexResolution, out ulong dataResolution)
+ {
+ indexResolution = dataResolution = 0;
+
+ ErrorNumber indexError = ReadFluxIndexResolution(head, track, subTrack, captureIndex, out indexResolution);
+
+ if(indexError != ErrorNumber.NoError)
+ return indexError;
+
+ ErrorNumber dataError = ReadFluxDataResolution(head, track, subTrack, captureIndex, out dataResolution);
+
+ return dataError;
+ }
+
+ public ErrorNumber ReadFluxIndexCapture(uint head, ushort track, byte subTrack, uint captureIndex,
+ out byte[] buffer)
+ {
+ buffer = null;
+
+ if(Header.flags.HasFlag(ScpFlags.NotFloppy))
+ return ErrorNumber.NotImplemented;
+
+ if(captureIndex > 0)
+ return ErrorNumber.OutOfRange;
+
+ List tmpBuffer = new();
+
+ if(Header.flags.HasFlag(ScpFlags.StartsAtIndex))
+ tmpBuffer.Add(0);
+
+ TrackHeader scpTrack = ScpTracks[(byte)HeadTrackSubToScpTrack(head, track, subTrack)];
+
+ for(int i = 0; i < Header.revolutions; i++)
+ tmpBuffer.AddRange(UInt32ToFluxRepresentation(scpTrack.Entries[i].indexTime));
+
+ buffer = tmpBuffer.ToArray();
+
+ return ErrorNumber.NoError;
+ }
+
+ public ErrorNumber ReadFluxDataCapture(uint head, ushort track, byte subTrack, uint captureIndex, out byte[] buffer)
+ {
+ buffer = null;
+
+ if(Header.flags.HasFlag(ScpFlags.NotFloppy))
+ return ErrorNumber.NotImplemented;
+
+ if(HeadTrackSubToScpTrack(head, track, subTrack) > Header.end)
+ return ErrorNumber.OutOfRange;
+
+ if(captureIndex > 0)
+ return ErrorNumber.OutOfRange;
+
+ if(Header.bitCellEncoding != 0 &&
+ Header.bitCellEncoding != 16)
+ return ErrorNumber.NotImplemented;
+
+ TrackHeader scpTrack = ScpTracks[(byte)HeadTrackSubToScpTrack(head, track, subTrack)];
+
+ Stream stream = _scpFilter.GetDataForkStream();
+ var br = new BinaryReader(stream);
+
+ List tmpBuffer = new();
+
+ for(int i = 0; i < Header.revolutions; i++)
+ {
+ br.BaseStream.Seek(scpTrack.Entries[i].dataOffset, SeekOrigin.Begin);
+
+ // TODO: Check for 0x0000
+ for(ulong j = 0; j < scpTrack.Entries[i].trackLength; j++)
+ tmpBuffer.AddRange(UInt16ToFluxRepresentation(BigEndianBitConverter.ToUInt16(br.ReadBytes(2), 0)));
+ }
+
+ buffer = tmpBuffer.ToArray();
+
+ 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)
+ {
+ dataBuffer = indexBuffer = null;
+
+ ErrorNumber error =
+ ReadFluxResolution(head, track, subTrack, captureIndex, out indexResolution, out dataResolution);
+
+ if(error != ErrorNumber.NoError)
+ return error;
+
+ error = ReadFluxDataCapture(head, track, subTrack, captureIndex, out dataBuffer);
+
+ if(error != ErrorNumber.NoError)
+ return error;
+
+ ErrorNumber indexCapture = ReadFluxIndexCapture(head, track, subTrack, captureIndex, out indexBuffer);
+
+ return indexCapture;
+ }
}
\ No newline at end of file
diff --git a/Aaru.Images/SuperCardPro/Structs.cs b/Aaru.Images/SuperCardPro/Structs.cs
index 91e04b8b0..686b1ce36 100644
--- a/Aaru.Images/SuperCardPro/Structs.cs
+++ b/Aaru.Images/SuperCardPro/Structs.cs
@@ -47,10 +47,10 @@ public sealed partial class SuperCardPro
public byte start;
public byte end;
public ScpFlags flags;
- public byte bitCellEncoding;
- public byte heads;
- public byte reserved;
- public uint checksum;
+ public byte bitCellEncoding; // 0 = 16 bit, otherwise value represent bits
+ public byte heads; // 1 = bottom side, 2 = top side, 0 = both
+ public byte resolution; // 25ns increments, ergo algorithm is 25ns + resolution * 25ns
+ public uint checksum; // for data starting at 0x10
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 168)]
public uint[] offsets;
}
diff --git a/Aaru.Images/SuperCardPro/SuperCardPro.cs b/Aaru.Images/SuperCardPro/SuperCardPro.cs
index db5c48699..eacdcb806 100644
--- a/Aaru.Images/SuperCardPro/SuperCardPro.cs
+++ b/Aaru.Images/SuperCardPro/SuperCardPro.cs
@@ -30,6 +30,7 @@
// Copyright © 2011-2023 Natalia Portillo
// ****************************************************************************/
+using System;
using System.Collections.Generic;
using System.IO;
using Aaru.CommonTypes.Enums;
@@ -40,10 +41,14 @@ namespace Aaru.DiscImages;
///
/// Implements reading SuperCardPro flux images
-public sealed partial class SuperCardPro : IMediaImage, IVerifiableImage, IVerifiableSectorsImage
+public sealed partial class SuperCardPro : IFluxImage, IMediaImage, IWritableImage, IVerifiableImage,
+ IVerifiableSectorsImage, IWritableFluxImage
{
- ImageInfo _imageInfo;
- Stream _scpStream;
+ ImageInfo _imageInfo;
+ Stream _scpStream;
+ IFilter _scpFilter;
+ FileStream _writingStream;
+ uint _trackOffset;
// TODO: These variables have been made public so create-sidecar can access to this information until I define an API >4.0
public ScpHeader Header;
diff --git a/Aaru.Images/SuperCardPro/Write.cs b/Aaru.Images/SuperCardPro/Write.cs
new file mode 100644
index 000000000..8ea23f378
--- /dev/null
+++ b/Aaru.Images/SuperCardPro/Write.cs
@@ -0,0 +1,254 @@
+// /***************************************************************************
+// Aaru Data Preservation Suite
+// ----------------------------------------------------------------------------
+//
+// Filename : Write.cs
+// Author(s) : Rebecca Wallander
+//
+// Component : Disk image plugins.
+//
+// --[ Description ] ----------------------------------------------------------
+//
+// Writes SuperCardPro flux images.
+//
+// --[ License ] --------------------------------------------------------------
+//
+// This library is free software; you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as
+// published by the Free Software Foundation; either version 2.1 of the
+// License, or (at your option) any later version.
+//
+// This library 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
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, see .
+//
+// ----------------------------------------------------------------------------
+// Copyright © 2011-2023 Rebecca Wallander
+// ****************************************************************************/
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using Aaru.CommonTypes;
+using Aaru.CommonTypes.AaruMetadata;
+using Aaru.CommonTypes.Enums;
+using Aaru.CommonTypes.Structs;
+using Track = Aaru.CommonTypes.AaruMetadata.Track;
+
+namespace Aaru.DiscImages;
+
+public sealed partial class SuperCardPro
+{
+ public bool Create(string path, MediaType mediaType, Dictionary options, ulong sectors,
+ uint sectorSize)
+ {
+ try
+ {
+ _writingStream = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None);
+ }
+ catch(IOException e)
+ {
+ ErrorMessage = string.Format(Localization.Could_not_create_new_image_file_exception_0, e.Message);
+
+ return false;
+ }
+
+ _trackOffset = FULL_HEADER_OFFSET;
+
+ IsWriting = true;
+ ErrorMessage = null;
+
+ Header.signature = _scpSignature;
+
+ Header.type = mediaType switch
+ {
+ MediaType.DOS_525_DS_DD_9 => ScpDiskType.PC360K,
+ MediaType.Unknown => ScpDiskType.Generic720K,
+ _ => Header.type
+ };
+
+ return true;
+ }
+
+ public bool Close()
+ {
+ if(!IsWriting)
+ {
+ ErrorMessage = Localization.Image_is_not_opened_for_writing;
+
+ return false;
+ }
+
+ _writingStream.Seek(0, SeekOrigin.Begin);
+
+ _writingStream.Write(Header.signature, 0, 3);
+ _writingStream.WriteByte(Header.version);
+ _writingStream.WriteByte((byte)Header.type);
+ _writingStream.WriteByte(Header.revolutions);
+ _writingStream.WriteByte(Header.start);
+ _writingStream.WriteByte(Header.end);
+ _writingStream.WriteByte((byte)Header.flags);
+ _writingStream.WriteByte(Header.bitCellEncoding);
+ _writingStream.WriteByte(Header.heads);
+ _writingStream.WriteByte(Header.resolution);
+
+ _writingStream.Seek(0, SeekOrigin.End);
+ string date = DateTime.Now.ToString("G");
+ _writingStream.Write(Encoding.ASCII.GetBytes(date), 0, date.Length);
+
+ Header.checksum = CalculateChecksum(_writingStream);
+ _writingStream.Seek(0x0C, SeekOrigin.Begin);
+ _writingStream.Write(BitConverter.GetBytes(Header.checksum), 0, 4);
+
+ _writingStream.Flush();
+ _writingStream.Close();
+
+ IsWriting = false;
+ ErrorMessage = "";
+
+ return true;
+ }
+
+ public bool SetGeometry(uint cylinders, uint heads, uint sectorsPerTrack) => true;
+
+ public bool WriteSectorTag(byte[] data, ulong sectorAddress, SectorTagType tag) => false;
+
+ public bool WriteSectorsTag(byte[] data, ulong sectorAddress, uint length, SectorTagType tag) => false;
+
+ public bool SetDumpHardware(List dumpHardware) => false;
+
+ public bool SetMetadata(Metadata metadata) => false;
+
+ public bool WriteMediaTag(byte[] data, MediaTagType tag) => false;
+
+ public bool WriteSector(byte[] data, ulong sectorAddress)
+ {
+ ErrorMessage = Localization.Flux_decoding_is_not_yet_implemented;
+
+ return false;
+ }
+
+ public bool WriteSectors(byte[] data, ulong sectorAddress, uint length)
+ {
+ ErrorMessage = Localization.Flux_decoding_is_not_yet_implemented;
+
+ return false;
+ }
+
+ ///
+ public bool WriteSectorLong(byte[] data, ulong sectorAddress)
+ {
+ ErrorMessage = Localization.Flux_decoding_is_not_yet_implemented;
+
+ return false;
+ }
+
+ ///
+ public bool WriteSectorsLong(byte[] data, ulong sectorAddress, uint length)
+ {
+ ErrorMessage = Localization.Flux_decoding_is_not_yet_implemented;
+
+ return false;
+ }
+
+ public bool SetTracks(List