mirror of
https://github.com/aaru-dps/Aaru.git
synced 2025-12-16 11:14:25 +00:00
504 lines
20 KiB
C#
504 lines
20 KiB
C#
// /***************************************************************************
|
|
// 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.Diagnostics.CodeAnalysis;
|
|
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.Images;
|
|
|
|
[SuppressMessage("ReSharper", "UnusedType.Global")]
|
|
public sealed partial class A2R
|
|
{
|
|
#region IFluxImage Members
|
|
|
|
/// <inheritdoc />
|
|
public ErrorNumber Open(IFilter imageFilter)
|
|
{
|
|
_a2RStream = imageFilter.GetDataForkStream();
|
|
_a2RStream.Seek(0, SeekOrigin.Begin);
|
|
|
|
_a2RFilter = imageFilter;
|
|
|
|
var hdr = new byte[Marshal.SizeOf<A2RHeader>()];
|
|
_a2RStream.EnsureRead(hdr, 0, Marshal.SizeOf<A2RHeader>());
|
|
|
|
_header = Marshal.ByteArrayToStructureLittleEndian<A2RHeader>(hdr);
|
|
|
|
AaruConsole.DebugWriteLine(MODULE_NAME, "header.signature = \"{0}\"",
|
|
StringHandlers.CToString(_header.signature));
|
|
|
|
AaruConsole.DebugWriteLine(MODULE_NAME, "header.version = {0}", _header.version);
|
|
AaruConsole.DebugWriteLine(MODULE_NAME, "header.highBitTest = {0:X2}", _header.highBitTest);
|
|
|
|
AaruConsole.DebugWriteLine(MODULE_NAME, "header.lineTest = {0:X2} {1:X2} {2:X2}", _header.lineTest[0],
|
|
_header.lineTest[1], _header.lineTest[2]);
|
|
|
|
var 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:
|
|
{
|
|
var infoChnk = new byte[Marshal.SizeOf<InfoChunkV2>()];
|
|
_a2RStream.EnsureRead(infoChnk, 0, Marshal.SizeOf<InfoChunkV2>());
|
|
_infoChunkV2 = Marshal.ByteArrayToStructureLittleEndian<InfoChunkV2>(infoChnk);
|
|
|
|
AaruConsole.DebugWriteLine(MODULE_NAME, "infoChunk.header.chunkId = \"{0}\"",
|
|
StringHandlers.CToString(_infoChunkV2.header.chunkId));
|
|
|
|
AaruConsole.DebugWriteLine(MODULE_NAME, "infoChunk.header.chunkSize = {0}",
|
|
_infoChunkV2.header.chunkSize);
|
|
|
|
AaruConsole.DebugWriteLine(MODULE_NAME, "infoChunk.version = {0}", _infoChunkV2.version);
|
|
|
|
AaruConsole.DebugWriteLine(MODULE_NAME, "infoChunk.creator = \"{0}\"",
|
|
StringHandlers.CToString(_infoChunkV2.creator).TrimEnd());
|
|
|
|
AaruConsole.DebugWriteLine(MODULE_NAME, "infoChunk.diskType = {0}", _infoChunkV2.diskType);
|
|
|
|
AaruConsole.DebugWriteLine(MODULE_NAME, "infoChunk.writeProtected = {0}", _infoChunkV2.writeProtected);
|
|
|
|
AaruConsole.DebugWriteLine(MODULE_NAME, "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:
|
|
{
|
|
var infoChk = new byte[Marshal.SizeOf<InfoChunkV3>()];
|
|
_a2RStream.EnsureRead(infoChk, 0, Marshal.SizeOf<InfoChunkV3>());
|
|
_infoChunkV3 = Marshal.ByteArrayToStructureLittleEndian<InfoChunkV3>(infoChk);
|
|
|
|
AaruConsole.DebugWriteLine(MODULE_NAME, "infoChunk.header.chunkId = \"{0}\"",
|
|
StringHandlers.CToString(_infoChunkV3.header.chunkId));
|
|
|
|
AaruConsole.DebugWriteLine(MODULE_NAME, "infoChunk.header.chunkSize = {0}",
|
|
_infoChunkV3.header.chunkSize);
|
|
|
|
AaruConsole.DebugWriteLine(MODULE_NAME, "infoChunk.version = {0}", _infoChunkV3.version);
|
|
|
|
AaruConsole.DebugWriteLine(MODULE_NAME, "infoChunk.creator = \"{0}\"",
|
|
StringHandlers.CToString(_infoChunkV3.creator).TrimEnd());
|
|
|
|
AaruConsole.DebugWriteLine(MODULE_NAME, "infoChunk.driveType = {0}", _infoChunkV3.driveType);
|
|
|
|
AaruConsole.DebugWriteLine(MODULE_NAME, "infoChunk.writeProtected = {0}", _infoChunkV3.writeProtected);
|
|
|
|
AaruConsole.DebugWriteLine(MODULE_NAME, "infoChunk.synchronized = {0}", _infoChunkV3.synchronized);
|
|
|
|
AaruConsole.DebugWriteLine(MODULE_NAME, "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)
|
|
{
|
|
var 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):
|
|
var 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()
|
|
};
|
|
|
|
var 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(var i = 0; capture.numberOfIndexSignals > i; i++)
|
|
{
|
|
var index = new byte[4];
|
|
_a2RStream.EnsureRead(index, 0, 4);
|
|
capture.indexSignals[i] = BitConverter.ToUInt32(index);
|
|
}
|
|
|
|
var 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);
|
|
|
|
var 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[] keyValue in metaFields.Select(field => field.Split('\t')).
|
|
Where(keyValue => 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):
|
|
var 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);
|
|
|
|
var dataSize = new byte[4];
|
|
_a2RStream.EnsureRead(dataSize, 0, 4);
|
|
capture.captureDataSize = BitConverter.ToUInt32(dataSize);
|
|
|
|
var 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(var 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;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region IMediaImage Members
|
|
|
|
/// <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();
|
|
|
|
#endregion
|
|
|
|
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];
|
|
}
|
|
} |