Merge pull request #809 from aaru-dps/fakeshemp/a2r

Add A2R flux format support
This commit is contained in:
2023-07-09 21:15:50 +01:00
committed by GitHub
17 changed files with 6634 additions and 8030 deletions

View File

@@ -1,6 +1,6 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Aaru" type="DotNetProject" factoryName=".NET Project">
<option name="EXE_PATH" value="$PROJECT_DIR$/Aaru/bin/Debug/net7.0/aaru.exe" />
<option name="EXE_PATH" value="$PROJECT_DIR$/Aaru/bin/Debug/net7.0/aaru" />
<option name="PROGRAM_PARAMETERS" value="formats" />
<option name="WORKING_DIRECTORY" value="$USER_HOME$/Desktop" />
<option name="PASS_PARENT_ENVS" value="1" />

91
Aaru.Images/A2R/A2R.cs Normal file
View File

@@ -0,0 +1,91 @@
// /***************************************************************************
// Aaru Data Preservation Suite
// ----------------------------------------------------------------------------
//
// Filename : A2R.cs
// Author(s) : Rebecca Wallander <sakcheen@gmail.com>
//
// Component : Disk image plugins.
//
// --[ Description ] ----------------------------------------------------------
//
// Manages A2R 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
// ****************************************************************************/
// Version 2 documentation: https://web.archive.org/web/20200325131633/https://applesaucefdc.com/a2r/
// Version 3 documentation: https://web.archive.org/web/20220526215820/https://applesaucefdc.com/a2r/
using System.Collections.Generic;
using System.IO;
using Aaru.CommonTypes.Enums;
using Aaru.CommonTypes.Interfaces;
using Aaru.CommonTypes.Structs;
namespace Aaru.DiscImages;
/// <inheritdoc cref="Aaru.CommonTypes.Interfaces.IMediaImage" />
/// <summary>Implements reading A2R flux images</summary>
public sealed partial class A2R : IFluxImage, IMediaImage, IWritableImage, IWritableFluxImage
{
ImageInfo _imageInfo;
Stream _a2rStream;
IFilter _a2rFilter;
FileStream _writingStream;
A2rHeader Header;
Dictionary<string, string> Meta;
InfoChunkV2 _infoChunkV2;
InfoChunkV3 _infoChunkV3;
List<StreamCapture> _a2rCaptures;
uint _currentResolution;
// Offset from the start of the current RWCP to the next capture
uint _currentCaptureOffset = 16;
// 53 = A2R header, INFO header, INFO data
long _currentRwcpStart = 53;
public A2R()
{
_imageInfo = new ImageInfo
{
ReadableSectorTags = new List<SectorTagType>(),
ReadableMediaTags = new List<MediaTagType>(),
HasPartitions = false,
HasSessions = false,
Version = null,
Application = null,
ApplicationVersion = null,
Creator = null,
Comments = null,
MediaManufacturer = null,
MediaModel = null,
MediaSerialNumber = null,
MediaBarcode = null,
MediaPartNumber = null,
MediaSequence = 0,
LastMediaSequence = 0,
DriveManufacturer = null,
DriveModel = null,
DriveSerialNumber = null,
DriveFirmwareRevision = null
};
}
}

View File

@@ -0,0 +1,71 @@
// /***************************************************************************
// Aaru Data Preservation Suite
// ----------------------------------------------------------------------------
//
// Filename : Constants.cs
// Author(s) : Rebecca Wallander <sakcheen@gmail.com>
//
// Component : Disk image plugins.
//
// --[ Description ] ----------------------------------------------------------
//
// Contains constants for A2R 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
// ****************************************************************************/
namespace Aaru.DiscImages;
public sealed partial class A2R
{
readonly byte[] _a2rV2Signature =
{
0x41, 0x32, 0x52, 0x32 // A2R2
};
readonly byte[] _a2rV3Signature =
{
0x41, 0x32, 0x52, 0x33 // A2R3
};
readonly byte[] _infoChunkSignature =
{
0x49, 0x4E, 0x46, 0x4F // INFO
};
readonly byte[] _rwcpChunkSignature =
{
0x52, 0x57, 0x43, 0x50 // RWCP
};
readonly byte[] _slvdChunkSignature =
{
0x53, 0x4C, 0x56, 0x44 // SLVD
};
readonly byte[] _metaChunkSignature =
{
0x4D, 0x45, 0x54, 0x41 // META
};
readonly byte[] _strmChunkSignature =
{
0x53, 0x54, 0x52, 0x4D // STRM
};
}

50
Aaru.Images/A2R/Enums.cs Normal file
View File

@@ -0,0 +1,50 @@
// /***************************************************************************
// Aaru Data Preservation Suite
// ----------------------------------------------------------------------------
//
// Filename : Enums.cs
// Author(s) : Rebecca Wallander <sakcheen@gmail.com>
//
// Component : Disk image plugins.
//
// --[ Description ] ----------------------------------------------------------
//
// Contains enumerations for A2R 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.Diagnostics.CodeAnalysis;
namespace Aaru.DiscImages;
public sealed partial class A2R
{
[SuppressMessage("ReSharper", "InconsistentNaming")]
public enum A2rDriveType : byte
{
SS_525_40trk_quarterStep = 0x1, DS_35_80trk_appleCLV = 0x2, DS_525_80trk = 0x3,
DS_525_40trk = 0x4, DS_35_80trk = 0x5, DS_8 = 0x6
}
public enum A2rDiskType : byte
{
_525 = 0x01, _35 = 0x2,
}
}

165
Aaru.Images/A2R/Helpers.cs Normal file
View File

@@ -0,0 +1,165 @@
// /***************************************************************************
// Aaru Data Preservation Suite
// ----------------------------------------------------------------------------
//
// Filename : Helpers.cs
// Author(s) : Rebecca Wallander <sakcheen@gmail.com>
//
// Component : Disk image plugins.
//
// --[ Description ] ----------------------------------------------------------
//
// Contains helpers for A2R 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.Linq;
using Aaru.CommonTypes;
namespace Aaru.DiscImages;
public sealed partial class A2R
{
/// <summary>
/// Takes a Head, Track and Sub-Track representation, as well as the <c>MediaType</c>,
/// and converts it to the Track representation used by A2R.
/// </summary>
/// <param name="head">The head number</param>
/// <param name="track">The track number</param>
/// <param name="subTrack">The sub-track number</param>
/// <param name="mediaType">The media type of the image</param>
/// <returns>A2R format location</returns>
static long HeadTrackSubToA2rLocation(uint head, ushort track, byte subTrack, MediaType mediaType)
{
if(mediaType == MediaType.Apple32SS)
return head + (track * 4) + subTrack;
return head + (track * 2);
}
/// <summary>
/// Takes a Head, Track and Sub-Track representation, as well as the <c>A2rDriveType</c>,
/// and converts it to the Track representation used by A2R.
/// </summary>
/// <param name="head">The head number</param>
/// <param name="track">The track number</param>
/// <param name="subTrack">The sub-track number</param>
/// <param name="driveType">The drive type enum of the A2R image</param>
/// <returns>A2R format location</returns>
static long HeadTrackSubToA2rLocation(uint head, ushort track, byte subTrack, A2rDriveType driveType)
{
if(driveType == A2rDriveType.SS_525_40trk_quarterStep)
return head + (track * 4) + subTrack;
return head + (track * 2);
}
/// <summary>
/// Takes an A2R location and a <c>MediaType</c>, and converts it to a Head, Track and Sub-Track representation
/// used by the internal representation. The <c>MediaType</c> is needed because the track location is different
/// for different types of media sources.
/// </summary>
/// <param name="location">A2R format location</param>
/// <param name="mediaType"></param>
/// <param name="head">The head number</param>
/// <param name="track">The track number</param>
/// <param name="subTrack">The sub-track number</param>
static void A2rLocationToHeadTrackSub(uint location, MediaType mediaType, out uint head, out ushort track,
out byte subTrack)
{
if(mediaType == MediaType.Apple32SS)
{
head = 0;
track = (ushort)(location / 4);
subTrack = (byte)(location % 4);
return;
}
head = location % 2;
track = (ushort)((location - head) / 2);
subTrack = 0;
}
/// <summary>
/// Takes a single number flux (uint length) and converts it to a flux in the
/// internal representation format (byte length)
/// </summary>
/// <param name="ticks">The <c>uint</c> flux representation</param>
/// <returns>The <c>byte[]</c> flux representation</returns>
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;
}
/// <summary>
/// Takes a flux representation in the internal format (byte length) and converts it to
/// an array of single number fluxes (uint length)
/// </summary>
/// <param name="flux">The <c>byte[]</c> flux representation</param>
/// <returns>The <c>uint</c> flux representation</returns>
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;
}
/// <summary>
/// A2R has two types of flux capture types; "timing" and "xtiming". The only difference is the length of the
/// capture, with "timing" being about 1¼ revolutions. This function returns <c>true</c> if the flux buffer is "timing"
/// and <c>false</c> otherwise.
/// </summary>
/// <param name="resolution">The resolution of the flux capture</param>
/// <param name="buffer">The flux data</param>
/// <returns><c>true</c> if "timing", <c>false</c> if "xtiming"</returns>
static bool IsCaptureTypeTiming(ulong resolution, byte[] buffer) =>
// TODO: This is only accurate for 300rpm
buffer.Select(static x => (int)x).Sum() * (long)resolution is > 230000000000 and < 270000000000;
}

View File

@@ -0,0 +1,57 @@
// /***************************************************************************
// Aaru Data Preservation Suite
// ----------------------------------------------------------------------------
//
// Filename : Identify.cs
// Author(s) : Rebecca Wallander <sakcheen@gmail.com>
//
// Component : Disk image plugins.
//
// --[ Description ] ----------------------------------------------------------
//
// Identifies A2R 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.IO;
using System.Linq;
using Aaru.CommonTypes.Interfaces;
using Aaru.Helpers;
namespace Aaru.DiscImages;
public sealed partial class A2R
{
/// <inheritdoc />
public bool Identify(IFilter imageFilter)
{
Stream stream = imageFilter.GetDataForkStream();
stream.Seek(0, SeekOrigin.Begin);
if(stream.Length < 8)
return false;
byte[] hdr = new byte[4];
stream.EnsureRead(hdr, 0, 4);
return _a2rV2Signature.SequenceEqual(hdr) || _a2rV3Signature.SequenceEqual(hdr);
}
}

View File

@@ -0,0 +1,84 @@
// /***************************************************************************
// Aaru Data Preservation Suite
// ----------------------------------------------------------------------------
//
// Filename : Properties.cs
// Author(s) : Rebecca Wallander <sakcheen@gmail.com>
//
// Component : Disk image plugins.
//
// --[ Description ] ----------------------------------------------------------
//
// Contains properties for A2R 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 Aaru.CommonTypes;
using Aaru.CommonTypes.AaruMetadata;
using Aaru.CommonTypes.Enums;
using Aaru.CommonTypes.Structs;
namespace Aaru.DiscImages;
public sealed partial class A2R
{
/// <inheritdoc />
public ImageInfo Info => _imageInfo;
/// <inheritdoc />
public string Name => Localization.A2R_Name;
/// <inheritdoc />
public Guid Id => new("7497c26a-fe44-4b50-a2e6-de50a9f3c13f");
/// <inheritdoc />
public string Author => Authors.RebeccaWallander;
/// <inheritdoc />
public string Format => "A2R";
/// <inheritdoc />
public List<DumpHardware> DumpHardware => null;
/// <inheritdoc />
public Metadata AaruMetadata => null;
/// <inheritdoc />
public IEnumerable<string> KnownExtensions => new[]
{
".a2r"
};
/// <inheritdoc />
public IEnumerable<MediaTagType> SupportedMediaTags => null;
/// <inheritdoc />
public IEnumerable<MediaType> SupportedMediaTypes => new[]
{
// TODO: A2R 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.Apple32SS,
MediaType.Unknown
};
/// <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<SectorTagType> SupportedSectorTags => Array.Empty<SectorTagType>();
/// <inheritdoc />
public bool IsWriting { get; private set; }
/// <inheritdoc />
public string ErrorMessage { get; private set; }
bool IsWritingRwcps { get; set; }
}

495
Aaru.Images/A2R/Read.cs Normal file
View File

@@ -0,0 +1,495 @@
// /***************************************************************************
// Aaru Data Preservation Suite
// ----------------------------------------------------------------------------
//
// Filename : Read.cs
// Author(s) : Rebecca Wallander <sakcheen@gmail.com>
//
// Component : Disk image plugins.
//
// --[ Description ] ----------------------------------------------------------
//
// Reads A2R 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.Linq;
using System.Text;
using Aaru.CommonTypes;
using Aaru.CommonTypes.Enums;
using Aaru.CommonTypes.Interfaces;
using Aaru.Console;
using Aaru.Helpers;
namespace Aaru.DiscImages;
public sealed partial class A2R
{
/// <inheritdoc />
public ErrorNumber Open(IFilter imageFilter)
{
_a2rStream = imageFilter.GetDataForkStream();
_a2rStream.Seek(0, SeekOrigin.Begin);
_a2rFilter = imageFilter;
byte[] hdr = new byte[Marshal.SizeOf<A2rHeader>()];
_a2rStream.EnsureRead(hdr, 0, Marshal.SizeOf<A2rHeader>());
Header = Marshal.ByteArrayToStructureLittleEndian<A2rHeader>(hdr);
AaruConsole.DebugWriteLine("A2R plugin", "header.signature = \"{0}\"",
StringHandlers.CToString(Header.signature));
AaruConsole.DebugWriteLine("A2R plugin", "header.version = {0}", Header.version);
AaruConsole.DebugWriteLine("A2R plugin", "header.highBitTest = {0:X2}", Header.highBitTest);
AaruConsole.DebugWriteLine("A2R plugin", "header.lineTest = {0:X2} {1:X2} {2:X2}", Header.lineTest[0],
Header.lineTest[1], Header.lineTest[2]);
byte[] infoMagic = new byte[4];
_a2rStream.EnsureRead(infoMagic, 0, 4);
// There must be an INFO chunk after the header (at byte 16)
if(!_infoChunkSignature.SequenceEqual(infoMagic))
return ErrorNumber.UnrecognizedFormat;
_a2rStream.Seek(-4, SeekOrigin.Current);
switch(Header.version)
{
case 0x32:
{
byte[] infoChnk = new byte[Marshal.SizeOf<InfoChunkV2>()];
_a2rStream.EnsureRead(infoChnk, 0, Marshal.SizeOf<InfoChunkV2>());
_infoChunkV2 = Marshal.ByteArrayToStructureLittleEndian<InfoChunkV2>(infoChnk);
AaruConsole.DebugWriteLine("A2R plugin", "infoChunk.header.chunkId = \"{0}\"",
StringHandlers.CToString(_infoChunkV2.header.chunkId));
AaruConsole.DebugWriteLine("A2R plugin", "infoChunk.header.chunkSize = {0}",
_infoChunkV2.header.chunkSize);
AaruConsole.DebugWriteLine("A2R plugin", "infoChunk.version = {0}", _infoChunkV2.version);
AaruConsole.DebugWriteLine("A2R plugin", "infoChunk.creator = \"{0}\"",
StringHandlers.CToString(_infoChunkV2.creator).TrimEnd());
AaruConsole.DebugWriteLine("A2R plugin", "infoChunk.diskType = {0}", _infoChunkV2.diskType);
AaruConsole.DebugWriteLine("A2R plugin", "infoChunk.writeProtected = {0}", _infoChunkV2.writeProtected);
AaruConsole.DebugWriteLine("A2R plugin", "infoChunk.synchronized = {0}", _infoChunkV2.synchronized);
_imageInfo.Creator = Encoding.ASCII.GetString(_infoChunkV2.creator).TrimEnd();
switch(_infoChunkV2.diskType)
{
case A2rDiskType._35:
_imageInfo.Heads = 2;
_imageInfo.Cylinders = 80;
_imageInfo.MediaType = MediaType.AppleSonyDS;
_imageInfo.SectorsPerTrack = 10;
break;
case A2rDiskType._525:
_imageInfo.Heads = 1;
_imageInfo.Cylinders = 40;
_imageInfo.MediaType = MediaType.Apple32SS;
break;
default: return ErrorNumber.OutOfRange;
}
break;
}
case 0x33:
{
byte[] infoChk = new byte[Marshal.SizeOf<InfoChunkV3>()];
_a2rStream.EnsureRead(infoChk, 0, Marshal.SizeOf<InfoChunkV3>());
_infoChunkV3 = Marshal.ByteArrayToStructureLittleEndian<InfoChunkV3>(infoChk);
AaruConsole.DebugWriteLine("A2R plugin", "infoChunk.header.chunkId = \"{0}\"",
StringHandlers.CToString(_infoChunkV3.header.chunkId));
AaruConsole.DebugWriteLine("A2R plugin", "infoChunk.header.chunkSize = {0}",
_infoChunkV3.header.chunkSize);
AaruConsole.DebugWriteLine("A2R plugin", "infoChunk.version = {0}", _infoChunkV3.version);
AaruConsole.DebugWriteLine("A2R plugin", "infoChunk.creator = \"{0}\"",
StringHandlers.CToString(_infoChunkV3.creator).TrimEnd());
AaruConsole.DebugWriteLine("A2R plugin", "infoChunk.driveType = {0}", _infoChunkV3.driveType);
AaruConsole.DebugWriteLine("A2R plugin", "infoChunk.writeProtected = {0}", _infoChunkV3.writeProtected);
AaruConsole.DebugWriteLine("A2R plugin", "infoChunk.synchronized = {0}", _infoChunkV3.synchronized);
AaruConsole.DebugWriteLine("A2R plugin", "infoChunk.hardSectorCount = {0}",
_infoChunkV3.hardSectorCount);
_imageInfo.Creator = Encoding.ASCII.GetString(_infoChunkV3.creator).TrimEnd();
switch(_infoChunkV3.driveType)
{
case A2rDriveType.SS_525_40trk_quarterStep:
_imageInfo.Heads = 1;
_imageInfo.Cylinders = 40;
_imageInfo.MediaType = MediaType.Apple32SS;
break;
case A2rDriveType.DS_35_80trk_appleCLV:
_imageInfo.Heads = 2;
_imageInfo.Cylinders = 80;
_imageInfo.MediaType = MediaType.AppleSonyDS;
_imageInfo.SectorsPerTrack = 10;
break;
case A2rDriveType.DS_525_80trk:
_imageInfo.Heads = 2;
_imageInfo.Cylinders = 80;
_imageInfo.MediaType = MediaType.DOS_525_HD;
break;
case A2rDriveType.DS_525_40trk:
_imageInfo.Heads = 2;
_imageInfo.Cylinders = 40;
_imageInfo.MediaType = MediaType.DOS_525_DS_DD_9;
_imageInfo.SectorsPerTrack = 9;
break;
case A2rDriveType.DS_35_80trk:
_imageInfo.Heads = 2;
_imageInfo.Cylinders = 80;
_imageInfo.MediaType = MediaType.DOS_35_HD;
_imageInfo.SectorsPerTrack = 18;
break;
case A2rDriveType.DS_8:
_imageInfo.Heads = 2;
_imageInfo.Cylinders = 40;
break;
default: return ErrorNumber.OutOfRange;
}
break;
}
}
_a2rCaptures = new List<StreamCapture>();
while(_a2rStream.Position < _a2rStream.Length)
{
byte[] chunkHdr = new byte[Marshal.SizeOf<ChunkHeader>()];
_a2rStream.EnsureRead(chunkHdr, 0, Marshal.SizeOf<ChunkHeader>());
ChunkHeader chunkHeader = Marshal.ByteArrayToStructureLittleEndian<ChunkHeader>(chunkHdr);
_a2rStream.Seek(-Marshal.SizeOf<ChunkHeader>(), SeekOrigin.Current);
switch(chunkHeader.chunkId)
{
case var rwcp when rwcp.SequenceEqual(_rwcpChunkSignature):
byte[] rwcpBuffer = new byte[Marshal.SizeOf<RwcpChunkHeader>()];
_a2rStream.EnsureRead(rwcpBuffer, 0, Marshal.SizeOf<RwcpChunkHeader>());
RwcpChunkHeader rwcpChunk = Marshal.ByteArrayToStructureLittleEndian<RwcpChunkHeader>(rwcpBuffer);
while(_a2rStream.ReadByte() == 0x43)
{
var capture = new StreamCapture
{
mark = 0x43,
captureType = (byte)_a2rStream.ReadByte()
};
byte[] location = new byte[2];
_a2rStream.EnsureRead(location, 0, 2);
capture.location = BitConverter.ToUInt16(location);
A2rLocationToHeadTrackSub(capture.location, _imageInfo.MediaType, out capture.head,
out capture.track, out capture.subTrack);
if(capture.head + 1 > _imageInfo.Heads)
_imageInfo.Heads = capture.head + 1;
if(capture.track + 1 > _imageInfo.Cylinders)
_imageInfo.Cylinders = (uint)(capture.track + 1);
capture.numberOfIndexSignals = (byte)_a2rStream.ReadByte();
capture.indexSignals = new uint[capture.numberOfIndexSignals];
for(int i = 0; capture.numberOfIndexSignals > i; i++)
{
byte[] index = new byte[4];
_a2rStream.EnsureRead(index, 0, 4);
capture.indexSignals[i] = BitConverter.ToUInt32(index);
}
byte[] dataSize = new byte[4];
_a2rStream.EnsureRead(dataSize, 0, 4);
capture.captureDataSize = BitConverter.ToUInt32(dataSize);
capture.dataOffset = _a2rStream.Position;
capture.resolution = rwcpChunk.resolution;
_a2rCaptures.Add(capture);
_a2rStream.Seek(capture.captureDataSize, SeekOrigin.Current);
}
break;
case var meta when meta.SequenceEqual(_metaChunkSignature):
Meta = new Dictionary<string, string>();
_a2rStream.Seek(Marshal.SizeOf<ChunkHeader>(), SeekOrigin.Current);
byte[] metadataBuffer = new byte[chunkHeader.chunkSize];
_a2rStream.EnsureRead(metadataBuffer, 0, (int)chunkHeader.chunkSize);
string metaData = Encoding.UTF8.GetString(metadataBuffer);
string[] metaFields = metaData.Split('\n');
foreach(string field in metaFields)
{
string[] keyValue = field.Split('\t');
if(keyValue.Length == 2)
Meta.Add(keyValue[0], keyValue[1]);
}
if(Meta.TryGetValue("image_date", out string imageDate))
_imageInfo.CreationTime = DateTime.Parse(imageDate);
if(Meta.TryGetValue("title", out string title))
_imageInfo.MediaTitle = title;
break;
case var slvd when slvd.SequenceEqual(_slvdChunkSignature): return ErrorNumber.NotImplemented;
case var strm when strm.SequenceEqual(_strmChunkSignature):
byte[] strmBuffer = new byte[Marshal.SizeOf<ChunkHeader>()];
_a2rStream.EnsureRead(strmBuffer, 0, Marshal.SizeOf<ChunkHeader>());
ChunkHeader strmChunk = Marshal.ByteArrayToStructureLittleEndian<ChunkHeader>(strmBuffer);
long end = strmChunk.chunkSize + _a2rStream.Position - 1;
while(_a2rStream.Position < end)
{
var capture = new StreamCapture
{
indexSignals = new uint[1],
location = (byte)_a2rStream.ReadByte(),
captureType = (byte)_a2rStream.ReadByte(),
resolution = 125000,
numberOfIndexSignals = 1
};
A2rLocationToHeadTrackSub(capture.location, _imageInfo.MediaType, out capture.head,
out capture.track, out capture.subTrack);
if(capture.head + 1 > _imageInfo.Heads)
_imageInfo.Heads = capture.head + 1;
if(capture.track + 1 > _imageInfo.Cylinders)
_imageInfo.Cylinders = (uint)(capture.track + 1);
byte[] dataSize = new byte[4];
_a2rStream.EnsureRead(dataSize, 0, 4);
capture.captureDataSize = BitConverter.ToUInt32(dataSize);
byte[] index = new byte[4];
_a2rStream.EnsureRead(index, 0, 4);
capture.indexSignals[0] = BitConverter.ToUInt32(index);
capture.dataOffset = _a2rStream.Position;
_a2rCaptures.Add(capture);
_a2rStream.Seek(capture.captureDataSize, SeekOrigin.Current);
}
_a2rStream.ReadByte();
break;
}
}
return ErrorNumber.NoError;
}
/// <inheritdoc />
public ErrorNumber CapturesLength(uint head, ushort track, byte subTrack, out uint length)
{
long index = HeadTrackSubToA2rLocation(head, track, subTrack, _imageInfo.MediaType);
length = (uint)_a2rCaptures.FindAll(capture => index == capture.location).Count;
return ErrorNumber.NoError;
}
/// <inheritdoc />
public ErrorNumber ReadFluxIndexResolution(uint head, ushort track, byte subTrack, uint captureIndex,
out ulong resolution)
{
resolution = StreamCaptureAtIndex(head, track, subTrack, captureIndex).resolution;
return ErrorNumber.NoError;
}
/// <inheritdoc />
public ErrorNumber ReadFluxDataResolution(uint head, ushort track, byte subTrack, uint captureIndex,
out ulong resolution)
{
resolution = StreamCaptureAtIndex(head, track, subTrack, captureIndex).resolution;
return ErrorNumber.NoError;
}
/// <inheritdoc />
public ErrorNumber ReadFluxResolution(uint head, ushort track, byte subTrack, uint captureIndex,
out ulong indexResolution, out ulong dataResolution)
{
indexResolution = dataResolution = StreamCaptureAtIndex(head, track, subTrack, captureIndex).resolution;
return ErrorNumber.NoError;
}
/// <inheritdoc />
public ErrorNumber ReadFluxCapture(uint head, ushort track, byte subTrack, uint captureIndex,
out ulong indexResolution, out ulong dataResolution, out byte[] indexBuffer,
out byte[] dataBuffer)
{
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;
error = ReadFluxIndexCapture(head, track, subTrack, captureIndex, out indexBuffer);
return error;
}
/// <inheritdoc />
public ErrorNumber ReadFluxIndexCapture(uint head, ushort track, byte subTrack, uint captureIndex,
out byte[] buffer)
{
buffer = null;
List<byte> tmpBuffer = new()
{
// A2R always starts at index signal
0
};
StreamCapture capture = StreamCaptureAtIndex(head, track, subTrack, captureIndex);
uint previousTicks = 0;
for(int i = 0; i < capture.numberOfIndexSignals; i++)
{
uint ticks = capture.indexSignals[i] - previousTicks;
tmpBuffer.AddRange(UInt32ToFluxRepresentation(ticks));
previousTicks = capture.indexSignals[i];
}
buffer = tmpBuffer.ToArray();
return ErrorNumber.NoError;
}
/// <inheritdoc />
public ErrorNumber ReadFluxDataCapture(uint head, ushort track, byte subTrack, uint captureIndex, out byte[] buffer)
{
buffer = null;
StreamCapture capture = StreamCaptureAtIndex(head, track, subTrack, captureIndex);
if(capture.captureType == 2)
return ErrorNumber.NotImplemented;
Stream stream = _a2rFilter.GetDataForkStream();
var br = new BinaryReader(stream);
br.BaseStream.Seek(capture.dataOffset, SeekOrigin.Begin);
buffer = br.ReadBytes((int)capture.captureDataSize);
return ErrorNumber.NoError;
}
/// <inheritdoc />
public ErrorNumber SubTrackLength(uint head, ushort track, out byte length)
{
length = 0;
List<StreamCapture> captures = _a2rCaptures.FindAll(c => c.head == head && c.track == track);
if(captures.Count <= 0)
return ErrorNumber.OutOfRange;
length = (byte)(captures.Max(static c => c.subTrack) + 1);
return ErrorNumber.NoError;
}
/// <inheritdoc />
public ErrorNumber ReadMediaTag(MediaTagType tag, out byte[] buffer) => throw new NotImplementedException();
/// <inheritdoc />
public ErrorNumber ReadSector(ulong sectorAddress, out byte[] buffer) => throw new NotImplementedException();
/// <inheritdoc />
public ErrorNumber ReadSectorLong(ulong sectorAddress, out byte[] buffer) => throw new NotImplementedException();
/// <inheritdoc />
public ErrorNumber ReadSectors(ulong sectorAddress, uint length, out byte[] buffer) =>
throw new NotImplementedException();
/// <inheritdoc />
public ErrorNumber ReadSectorsLong(ulong sectorAddress, uint length, out byte[] buffer) =>
throw new NotImplementedException();
/// <inheritdoc />
public ErrorNumber ReadSectorsTag(ulong sectorAddress, uint length, SectorTagType tag, out byte[] buffer) =>
throw new NotImplementedException();
/// <inheritdoc />
public ErrorNumber ReadSectorTag(ulong sectorAddress, SectorTagType tag, out byte[] buffer) =>
throw new NotImplementedException();
StreamCapture StreamCaptureAtIndex(uint head, ushort track, byte subTrack, uint captureIndex)
{
long index = HeadTrackSubToA2rLocation(head, track, subTrack, _imageInfo.MediaType);
return _a2rCaptures.FindAll(capture => index == capture.location)[(int)captureIndex];
}
}

132
Aaru.Images/A2R/Structs.cs Normal file
View File

@@ -0,0 +1,132 @@
// /***************************************************************************
// Aaru Data Preservation Suite
// ----------------------------------------------------------------------------
//
// Filename : Structs.cs
// Author(s) : Rebecca Wallander <sakcheen@gmail.com>
//
// Component : Disk image plugins.
//
// --[ Description ] ----------------------------------------------------------
//
// Contains structures for A2R 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.Runtime.InteropServices;
namespace Aaru.DiscImages;
public sealed partial class A2R
{
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct A2rHeader
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
public byte[] signature;
public byte version;
public byte highBitTest; // Should always be 0xFF
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
public byte[] lineTest; // Should always be 0x0A 0x0D 0x0A
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct ChunkHeader
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
public byte[] chunkId;
public uint chunkSize;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct InfoChunkV2
{
public ChunkHeader header;
public byte version;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
public byte[] creator;
public A2rDiskType diskType;
public byte writeProtected;
public byte synchronized;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct InfoChunkV3
{
public ChunkHeader header;
public byte version;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
public byte[] creator;
public A2rDriveType driveType;
public byte writeProtected;
public byte synchronized;
public byte hardSectorCount;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct RwcpChunkHeader
{
public ChunkHeader header;
public byte version;
public uint resolution;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 11)]
public byte[] reserved;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct StreamCapture
{
public byte mark;
public byte captureType;
public ushort location;
public byte numberOfIndexSignals;
public uint[] indexSignals;
public uint captureDataSize;
public long dataOffset;
public uint resolution;
public uint head;
public ushort track;
public byte subTrack;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct SlvdChunkHeader
{
public ChunkHeader header;
public byte version;
public uint resolution;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 11)]
public byte[] reserved;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct TrackHeader
{
public byte mark;
public ushort location;
public byte mirrorDistanceOutward;
public byte mirrorDistanceInward;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
public byte[] reserved;
public byte numberOfIndexSignals;
public uint[] indexSignals;
public uint fluxDataSize;
}
}

338
Aaru.Images/A2R/Write.cs Normal file
View File

@@ -0,0 +1,338 @@
// /***************************************************************************
// Aaru Data Preservation Suite
// ----------------------------------------------------------------------------
//
// Filename : Write.cs
// Author(s) : Rebecca Wallander <sakcheen@gmail.com>
//
// Component : Disk image plugins.
//
// --[ Description ] ----------------------------------------------------------
//
// Writes A2R 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.Linq;
using System.Text;
using Aaru.CommonTypes;
using Aaru.CommonTypes.AaruMetadata;
using Aaru.CommonTypes.Enums;
using Aaru.CommonTypes.Structs;
using Aaru.Helpers;
namespace Aaru.DiscImages;
public sealed partial class A2R
{
/// <inheritdoc />
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;
}
IsWriting = true;
ErrorMessage = null;
Header.signature = "A2R"u8.ToArray();
Header.version = 0x33;
Header.highBitTest = 0xFF;
Header.lineTest = "\n\r\n"u8.ToArray();
_infoChunkV3.driveType = mediaType switch
{
MediaType.DOS_525_DS_DD_9 => A2rDriveType.DS_525_40trk,
MediaType.Apple32SS => A2rDriveType.SS_525_40trk_quarterStep,
MediaType.Unknown => A2rDriveType.DS_35_80trk,
_ => _infoChunkV3.driveType
};
return true;
}
/// <inheritdoc />
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(Header.highBitTest);
_writingStream.Write(Header.lineTest, 0, 3);
// First chunk needs to be an INFO chunk
WriteInfoChunk();
_writingStream.Seek(_currentRwcpStart, SeekOrigin.Begin);
WriteRwcpHeader();
_writingStream.Seek(0, SeekOrigin.End);
CloseRwcpChunk();
WriteMetaChunk();
_writingStream.Flush();
_writingStream.Close();
IsWriting = false;
ErrorMessage = "";
return true;
}
/// <inheritdoc />
public bool SetImageInfo(ImageInfo imageInfo)
{
Meta = new Dictionary<string, string>();
_infoChunkV3.header.chunkId = _infoChunkSignature;
_infoChunkV3.header.chunkSize = 37;
_infoChunkV3.version = 1;
_infoChunkV3.creator =
Encoding.UTF8.GetBytes($"Aaru v{typeof(A2R).Assembly.GetName().Version?.ToString()}".PadRight(32, ' '));
_infoChunkV3.writeProtected = 1;
_infoChunkV3.synchronized = 1;
_infoChunkV3.hardSectorCount = 0;
Meta.Add("image_date", DateTime.Now.ToString("O"));
Meta.Add("title", imageInfo.MediaTitle);
return true;
}
/// <inheritdoc />
public bool SetGeometry(uint cylinders, uint heads, uint sectorsPerTrack) => true;
/// <inheritdoc />
public bool WriteSectorTag(byte[] data, ulong sectorAddress, SectorTagType tag) => false;
/// <inheritdoc />
public bool WriteSectorsTag(byte[] data, ulong sectorAddress, uint length, SectorTagType tag) => false;
/// <inheritdoc />
public bool SetDumpHardware(List<DumpHardware> dumpHardware) => false;
/// <inheritdoc />
public bool SetMetadata(Metadata metadata) => false;
/// <inheritdoc />
public bool WriteMediaTag(byte[] data, MediaTagType tag) => false;
/// <inheritdoc />
public bool WriteSector(byte[] data, ulong sectorAddress) => throw new NotImplementedException();
/// <inheritdoc />
public bool WriteSectorLong(byte[] data, ulong sectorAddress) => throw new NotImplementedException();
/// <inheritdoc />
public bool WriteSectors(byte[] data, ulong sectorAddress, uint length) => throw new NotImplementedException();
/// <inheritdoc />
public bool WriteSectorsLong(byte[] data, ulong sectorAddress, uint length) => throw new NotImplementedException();
/// <inheritdoc />
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;
}
// An RWCP chunk can only have one capture resolution. If the resolution changes we need to create a new chunk.
if(_currentResolution != dataResolution)
{
if(IsWritingRwcps)
{
CloseRwcpChunk();
_writingStream.Seek(_currentRwcpStart, SeekOrigin.Begin);
WriteRwcpHeader();
_currentRwcpStart = _writingStream.Length;
_currentCaptureOffset = 16;
}
IsWritingRwcps = true;
_currentResolution = (uint)dataResolution;
}
_writingStream.Seek(_currentRwcpStart + _currentCaptureOffset + Marshal.SizeOf<ChunkHeader>(),
SeekOrigin.Begin);
_writingStream.WriteByte(0x43);
_writingStream.WriteByte(IsCaptureTypeTiming(dataResolution, dataBuffer) ? (byte)1 : (byte)3);
_writingStream.
Write(BitConverter.GetBytes((ushort)HeadTrackSubToA2rLocation(head, track, subTrack, _infoChunkV3.driveType)),
0, 2);
List<uint> a2rIndices = FluxRepresentationsToUInt32List(indexBuffer);
if(a2rIndices[0] == 0)
a2rIndices.RemoveAt(0);
_writingStream.WriteByte((byte)a2rIndices.Count);
long previousIndex = 0;
foreach(uint index in a2rIndices)
{
_writingStream.Write(BitConverter.GetBytes(index + previousIndex), 0, 4);
previousIndex += index;
}
_writingStream.Write(BitConverter.GetBytes(dataBuffer.Length), 0, 4);
_writingStream.Write(dataBuffer, 0, dataBuffer.Length);
_currentCaptureOffset += (uint)(9 + (a2rIndices.Count * 4) + dataBuffer.Length);
return ErrorNumber.NoError;
}
/// <inheritdoc />
public ErrorNumber WriteFluxIndexCapture(ulong resolution, byte[] index, uint head, ushort track, byte subTrack,
uint captureIndex) => ErrorNumber.NoError;
/// <inheritdoc />
public ErrorNumber WriteFluxDataCapture(ulong resolution, byte[] data, uint head, ushort track, byte subTrack,
uint captureIndex) => ErrorNumber.NoError;
/// <summary>
/// writes the header to an RWCP chunk, up to and including the reserved bytes, to stream.
/// </summary>
/// <returns></returns>
ErrorNumber WriteRwcpHeader()
{
if(!IsWriting)
{
ErrorMessage = Localization.Tried_to_write_on_a_non_writable_image;
return ErrorNumber.WriteError;
}
_writingStream.Write(_rwcpChunkSignature, 0, 4);
_writingStream.Write(BitConverter.GetBytes(_currentCaptureOffset + 1), 0, 4);
_writingStream.WriteByte(1);
_writingStream.Write(BitConverter.GetBytes(_currentResolution), 0, 4);
byte[] reserved =
{
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};
_writingStream.Write(reserved, 0, 11);
return ErrorNumber.NoError;
}
/// <summary>
/// Writes the entire INFO chunk to stream.
/// </summary>
/// <returns></returns>
ErrorNumber WriteInfoChunk()
{
if(!IsWriting)
{
ErrorMessage = Localization.Tried_to_write_on_a_non_writable_image;
return ErrorNumber.WriteError;
}
_writingStream.Write(_infoChunkV3.header.chunkId, 0, 4);
_writingStream.Write(BitConverter.GetBytes(_infoChunkV3.header.chunkSize), 0, 4);
_writingStream.WriteByte(_infoChunkV3.version);
_writingStream.Write(_infoChunkV3.creator, 0, 32);
_writingStream.WriteByte((byte)_infoChunkV3.driveType);
_writingStream.WriteByte(_infoChunkV3.writeProtected);
_writingStream.WriteByte(_infoChunkV3.synchronized);
_writingStream.WriteByte(_infoChunkV3.hardSectorCount);
return ErrorNumber.NoError;
}
/// <summary>
/// Writes the entire META chunk to stream.
/// </summary>
/// <returns></returns>
ErrorNumber WriteMetaChunk()
{
if(!IsWriting)
{
ErrorMessage = Localization.Tried_to_write_on_a_non_writable_image;
return ErrorNumber.WriteError;
}
_writingStream.Write(_metaChunkSignature, 0, 4);
byte[] metaString = Encoding.UTF8.GetBytes(Meta.Select(static m => $"{m.Key}\t{m.Value}").
Aggregate(static (concat, str) => $"{concat}\n{str}") + '\n');
_writingStream.Write(BitConverter.GetBytes((uint)metaString.Length), 0, 4);
_writingStream.Write(metaString, 0, metaString.Length);
return ErrorNumber.NoError;
}
/// <summary>
/// Writes the closing byte to an RWCP chunk signaling its end, to stream.
/// </summary>
/// <returns></returns>
ErrorNumber CloseRwcpChunk()
{
if(!IsWriting)
{
ErrorMessage = Localization.Tried_to_write_on_a_non_writable_image;
return ErrorNumber.WriteError;
}
_writingStream.WriteByte(0x58);
return ErrorNumber.NoError;
}
}

View File

@@ -33,6 +33,7 @@ namespace Aaru.DiscImages;
[SuppressMessage("ReSharper", "InconsistentNaming")]
static class Authors
{
internal const string MichaelDruing = "Michael Drüing";
internal const string NataliaPortillo = "Natalia Portillo";
internal const string MichaelDruing = "Michael Drüing";
internal const string NataliaPortillo = "Natalia Portillo";
internal const string RebeccaWallander = "Rebecca Wallander";
}

File diff suppressed because it is too large Load Diff

View File

@@ -2912,4 +2912,7 @@
<data name="Writing_Aaru_Metadata_block_to_position_0" xml:space="preserve">
<value>Writing Aaru Metadata block to position {0}</value>
</data>
<data name="A2R_Name" xml:space="preserve">
<value>A2R</value>
</data>
</root>

View File

@@ -255,13 +255,12 @@ public sealed partial class SuperCardPro
_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.TandySSDD: return ErrorNumber.NotImplemented;
case ScpDiskType.TandyDSSD: return ErrorNumber.NotImplemented;
case ScpDiskType.TandyDSDD: return ErrorNumber.NotImplemented;
case ScpDiskType.Ti994A: return ErrorNumber.NotImplemented;
case ScpDiskType.RolandD20: return ErrorNumber.NotImplemented;
case ScpDiskType.AmstradCPC: return ErrorNumber.NotImplemented;
case ScpDiskType.Generic360K:
_imageInfo.MediaType = MediaType.DOS_525_DS_DD_9;
_imageInfo.Cylinders = (uint)int.Max(((Header.end + 1) / 2), 40);

View File

@@ -223,7 +223,7 @@ public sealed partial class SuperCardPro
IsRevolutionsSet = true;
}
// SCP can only have the same number of revolutions for every tracks
// SCP can only have the same number of revolutions for all tracks
if(Header.revolutions != scpIndices.Count)
return ErrorNumber.NotSupported;

View File

@@ -1475,7 +1475,7 @@ sealed class ConvertImageCommand : Command
out byte[] dataBuffer);
outputFlux.WriteFluxCapture(indexResolution, dataResolution,
indexBuffer, dataBuffer, head, track, 0,
indexBuffer, dataBuffer, head, track, subTrackIndex,
captureIndex);
}
}