mirror of
https://github.com/aaru-dps/Aaru.git
synced 2025-12-16 19:24:25 +00:00
Add SuperCard Pro flux parsing
This commit is contained in:
Submodule Aaru.CommonTypes updated: a5fe3e6d91...69b26f6cd5
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
254
Aaru.Images/SuperCardPro/Write.cs
Normal file
254
Aaru.Images/SuperCardPro/Write.cs
Normal 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;
|
||||
}
|
||||
6
Aaru.Localization/Core.Designer.cs
generated
6
Aaru.Localization/Core.Designer.cs
generated
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user