Add SuperCard Pro flux parsing

This commit is contained in:
Rebecca Wallander
2023-04-13 21:06:57 +02:00
parent 5164a154e2
commit bf907ae19b
13 changed files with 811 additions and 35 deletions

View File

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

View File

@@ -46,4 +46,11 @@ public sealed partial class SuperCardPro
{
0x54, 0x52, 0x4B
};
/// <summary>SuperCardPro device default capture resolution: 25 nanoseconds.</summary>
const ushort DEFAULT_RESOLUTION = 25000;
/// <summary>SuperCardPro format full (with track offsets) header length.</summary>
const uint FULL_HEADER_OFFSET = 0x2b0;
/// <summary>SuperCardPro format header length (without track offsets).</summary>
const byte HEADER_OFFSET = 0x10;
}

View File

@@ -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
{
/// <summary>If set flux starts at index pulse</summary>
Index = 0x00,
/// <summary>If set drive is 96tpi</summary>
/// <summary>If set, flux starts at index pulse</summary>
StartsAtIndex = 0x00,
/// <summary>If set, drive is 96tpi, else 48tpi</summary>
Tpi = 0x02,
/// <summary>If set drive is 360rpm</summary>
/// <summary>If set, drive is 360rpm, else 300rpm</summary>
Rpm = 0x04,
/// <summary>If set image contains normalized data</summary>
/// <summary>If set, image contains normalized data</summary>
Normalized = 0x08,
/// <summary>If set image is read/write capable</summary>
/// <summary>If set, image is read/write capable</summary>
Writable = 0x10,
/// <summary>If set, image has footer</summary>
HasFooter = 0x20
HasFooter = 0x20,
/// <summary>If set, image is not floppy</summary>
NotFloppy = 0x40,
/// <summary>If set, image was not generated by SuperCard Pro device</summary>
CreatedByOtherDevice = 0x80
}
}

View File

@@ -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);
}
/// <summary>
/// Takes a Head, Track and Sub-Track representation and converts it to the Track representation used by SCP.
/// </summary>
/// <param name="head">The head number</param>
/// <param name="track">The track number</param>
/// <param name="subTrack">The sub-track number</param>
/// <returns>SCP format track number</returns>
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<uint> FluxRepresentationsToUInt32List(IEnumerable<byte> flux)
{
List<uint> 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<byte> FluxRepresentationsToUInt16List(IEnumerable<byte> flux, IReadOnlyList<uint> indices,
out uint[] trackLengths)
{
List<byte> scpData = new();
ushort tick = 0;
List<uint> 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;
}
}

View File

@@ -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> DumpHardware => null;
/// <inheritdoc />
public Metadata AaruMetadata => null;
/// <inheritdoc />
public IEnumerable<MediaTagType> SupportedMediaTags => null;
/// <inheritdoc />
public IEnumerable<MediaType> 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
};
/// <inheritdoc />
public IEnumerable<SectorTagType> SupportedSectorTags => Array.Empty<SectorTagType>();
/// <inheritdoc />
public IEnumerable<(string name, Type type, string description, object @default)> SupportedOptions =>
Array.Empty<(string name, Type type, string description, object @default)>();
/// <inheritdoc />
public IEnumerable<string> KnownExtensions => new[]
{
".scp"
};
/// <inheritdoc />
public bool IsWriting { get; private set; }
/// <inheritdoc />
public string ErrorMessage { get; private set; }
/// <summary>
/// SCP can only have one resolution. This is to help avoid changing the resolution and therefore create broken
/// SCP files.
/// </summary>
bool IsResolutionSet { get; set; }
/// <summary>
/// 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.
/// </summary>
bool IsRevolutionsSet { get; set; }
}

View File

@@ -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<ScpHeader>())
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;
}
/// <inheritdoc />
@@ -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<byte> 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<byte> 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;
}
}

View File

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

View File

@@ -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;
/// <inheritdoc cref="Aaru.CommonTypes.Interfaces.IMediaImage" />
/// <summary>Implements reading SuperCardPro flux images</summary>
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;

View File

@@ -0,0 +1,254 @@
// /***************************************************************************
// Aaru Data Preservation Suite
// ----------------------------------------------------------------------------
//
// Filename : Write.cs
// Author(s) : Rebecca Wallander <sakcheen@gmail.com>
//
// 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 <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------
// 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<string, string> 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> 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;
}
/// <inheritdoc />
public bool WriteSectorLong(byte[] data, ulong sectorAddress)
{
ErrorMessage = Localization.Flux_decoding_is_not_yet_implemented;
return false;
}
/// <inheritdoc />
public bool WriteSectorsLong(byte[] data, ulong sectorAddress, uint length)
{
ErrorMessage = Localization.Flux_decoding_is_not_yet_implemented;
return false;
}
public bool SetTracks(List<Track> tracks) => false;
public bool SetImageInfo(ImageInfo imageInfo)
{
string[] version = imageInfo.ApplicationVersion.Split('.');
Header.version = (byte)((byte.Parse(version[0]) << 4) + byte.Parse(version[1]));
Header.start = byte.MaxValue;
Header.end = byte.MinValue;
Header.bitCellEncoding = 0;
Header.heads = 0; // TODO: Support single sided disks
return true;
}
public ErrorNumber WriteFluxCapture(ulong indexResolution, ulong dataResolution, byte[] indexBuffer,
byte[] dataBuffer, uint head, ushort track, byte subTrack, uint captureIndex)
{
if(!IsWriting)
{
ErrorMessage = Localization.Tried_to_write_on_a_non_writable_image;
return ErrorNumber.WriteError;
}
if(subTrack != 0)
return ErrorNumber.NotSupported;
Header.start = byte.Min((byte)HeadTrackSubToScpTrack(head, track, subTrack), Header.start);
Header.end = byte.Max((byte)HeadTrackSubToScpTrack(head, track, subTrack), Header.end);
ulong scpResolution = (dataResolution / DEFAULT_RESOLUTION) - 1;
if(!IsResolutionSet)
{
Header.resolution = (byte)scpResolution;
IsResolutionSet = true;
}
// SCP can only have one resolution for all tracks
if(Header.resolution != scpResolution)
return ErrorNumber.NotSupported;
long scpTrack = HeadTrackSubToScpTrack(head, track, subTrack);
_writingStream.Seek(0x10 + (4 * scpTrack), SeekOrigin.Begin);
_writingStream.Write(BitConverter.GetBytes(_trackOffset), 0, 4);
_writingStream.Seek(_trackOffset, SeekOrigin.Begin);
_writingStream.Write(_trkSignature, 0, 3);
_writingStream.WriteByte((byte)scpTrack);
List<uint> scpIndices = FluxRepresentationsToUInt32List(indexBuffer);
if(scpIndices[0] == 0)
{
// Stream starts at index
Header.flags |= ScpFlags.StartsAtIndex;
scpIndices.RemoveAt(0);
}
if(!IsRevolutionsSet)
{
Header.revolutions = (byte)scpIndices.Count;
IsRevolutionsSet = true;
}
// SCP can only have the same number of revolutions for every tracks
if(Header.revolutions != scpIndices.Count)
return ErrorNumber.NotSupported;
List<byte> scpData = FluxRepresentationsToUInt16List(dataBuffer, scpIndices, out uint[] trackLengths);
uint offset = (uint)(4 + (12 * Header.revolutions));
for(int i = 0; i < Header.revolutions; i++)
{
_writingStream.Write(BitConverter.GetBytes(scpIndices[i]), 0, 4);
_writingStream.Write(BitConverter.GetBytes(trackLengths[i]), 0, 4);
_writingStream.Write(BitConverter.GetBytes(offset), 0, 4);
offset += trackLengths[i] * 2;
}
_writingStream.Write(scpData.ToArray(), 0, scpData.Count);
_trackOffset = (uint)_writingStream.Position;
return ErrorNumber.NoError;
}
public ErrorNumber WriteFluxIndexCapture(ulong resolution, byte[] index, uint head, ushort track, byte subTrack,
uint captureIndex) => ErrorNumber.NotImplemented;
public ErrorNumber WriteFluxDataCapture(ulong resolution, byte[] data, uint head, ushort track, byte subTrack,
uint captureIndex) => ErrorNumber.NotImplemented;
}

View File

@@ -6881,5 +6881,11 @@ namespace Aaru.Localization {
return ResourceManager.GetString("Output_format_does_not_support_hidden_tracks", resourceCulture);
}
}
public static string Image_flux_captures {
get {
return ResourceManager.GetString("Image_flux_captures", resourceCulture);
}
}
}
}

View File

@@ -3502,4 +3502,7 @@ It has no sense to do it, and it will put too much strain on the tape.</value>
<data name="Output_format_does_not_support_hidden_tracks" xml:space="preserve">
<value>Output format does not properly support storing hidden tracks, this will end in a loss of data, not continuing...</value>
</data>
<data name="Image_flux_captures" xml:space="preserve">
<value>This image contains low-level flux captures.</value>
</data>
</root>

View File

@@ -1388,8 +1388,6 @@ sealed class ConvertImageCommand : Command
while(doneSectors < inputFormat.Info.Sectors)
{
byte[] sector;
uint sectorsToDo;
if(inputFormat.Info.Sectors - doneSectors >= (ulong)count)
@@ -1403,7 +1401,8 @@ sealed class ConvertImageCommand : Command
bool result;
errno = sectorsToDo == 1 ? inputFormat.ReadSectorTag(doneSectors, tag, out sector)
errno = sectorsToDo == 1
? inputFormat.ReadSectorTag(doneSectors, tag, out byte[] sector)
: inputFormat.ReadSectorsTag(doneSectors, sectorsToDo, tag, out sector);
if(errno == ErrorNumber.NoError)
@@ -1447,6 +1446,43 @@ sealed class ConvertImageCommand : Command
tagsTask.StopTask();
}
if(inputFormat is IFluxImage inputFlux &&
outputFormat is IWritableFluxImage outputFlux)
{
for(ushort track = 0; track < inputFlux.Info.Cylinders; track++)
{
for(uint head = 0; head < inputFlux.Info.Heads; head++)
{
ErrorNumber error = inputFlux.SubTrackLength(head, track, out byte subTrackLen);
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, 0,
captureIndex);
}
}
}
}
}
if(inputTape == null ||
outputTape == null ||
!inputTape.IsTape)