mirror of
https://github.com/aaru-dps/Aaru.git
synced 2025-12-16 19:24:25 +00:00
Add A2R flux format support
This commit is contained in:
@@ -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" />
|
||||
|
||||
Submodule Aaru.CommonTypes updated: a10f12ebf9...d007f1ac9a
91
Aaru.Images/A2R/A2R.cs
Normal file
91
Aaru.Images/A2R/A2R.cs
Normal 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
|
||||
};
|
||||
}
|
||||
}
|
||||
71
Aaru.Images/A2R/Constants.cs
Normal file
71
Aaru.Images/A2R/Constants.cs
Normal 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
50
Aaru.Images/A2R/Enums.cs
Normal 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
165
Aaru.Images/A2R/Helpers.cs
Normal 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;
|
||||
}
|
||||
57
Aaru.Images/A2R/Identify.cs
Normal file
57
Aaru.Images/A2R/Identify.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
84
Aaru.Images/A2R/Properties.cs
Normal file
84
Aaru.Images/A2R/Properties.cs
Normal 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
495
Aaru.Images/A2R/Read.cs
Normal 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
132
Aaru.Images/A2R/Structs.cs
Normal 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
338
Aaru.Images/A2R/Write.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
13152
Aaru.Images/Localization/Localization.Designer.cs
generated
13152
Aaru.Images/Localization/Localization.Designer.cs
generated
File diff suppressed because it is too large
Load Diff
@@ -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>
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user