// /*************************************************************************** // Aaru Data Preservation Suite // ---------------------------------------------------------------------------- // // Filename : Read.cs // Author(s) : Rebecca Wallander // // 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 . // // ---------------------------------------------------------------------------- // 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 /// public ErrorNumber Open(IFilter imageFilter) { _a2RStream = imageFilter.GetDataForkStream(); _a2RStream.Seek(0, SeekOrigin.Begin); _a2RFilter = imageFilter; var hdr = new byte[Marshal.SizeOf()]; _a2RStream.EnsureRead(hdr, 0, Marshal.SizeOf()); _header = Marshal.ByteArrayToStructureLittleEndian(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()]; _a2RStream.EnsureRead(infoChnk, 0, Marshal.SizeOf()); _infoChunkV2 = Marshal.ByteArrayToStructureLittleEndian(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()]; _a2RStream.EnsureRead(infoChk, 0, Marshal.SizeOf()); _infoChunkV3 = Marshal.ByteArrayToStructureLittleEndian(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(); while(_a2RStream.Position < _a2RStream.Length) { var chunkHdr = new byte[Marshal.SizeOf()]; _a2RStream.EnsureRead(chunkHdr, 0, Marshal.SizeOf()); ChunkHeader chunkHeader = Marshal.ByteArrayToStructureLittleEndian(chunkHdr); _a2RStream.Seek(-Marshal.SizeOf(), SeekOrigin.Current); switch(chunkHeader.chunkId) { case var rwcp when rwcp.SequenceEqual(_rwcpChunkSignature): var rwcpBuffer = new byte[Marshal.SizeOf()]; _a2RStream.EnsureRead(rwcpBuffer, 0, Marshal.SizeOf()); RwcpChunkHeader rwcpChunk = Marshal.ByteArrayToStructureLittleEndian(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(); _a2RStream.Seek(Marshal.SizeOf(), 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()]; _a2RStream.EnsureRead(strmBuffer, 0, Marshal.SizeOf()); ChunkHeader strmChunk = Marshal.ByteArrayToStructureLittleEndian(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; } /// 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; } /// public ErrorNumber ReadFluxIndexResolution(uint head, ushort track, byte subTrack, uint captureIndex, out ulong resolution) { resolution = StreamCaptureAtIndex(head, track, subTrack, captureIndex).resolution; return ErrorNumber.NoError; } /// public ErrorNumber ReadFluxDataResolution(uint head, ushort track, byte subTrack, uint captureIndex, out ulong resolution) { resolution = StreamCaptureAtIndex(head, track, subTrack, captureIndex).resolution; return ErrorNumber.NoError; } /// 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; } /// 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; } /// public ErrorNumber ReadFluxIndexCapture(uint head, ushort track, byte subTrack, uint captureIndex, out byte[] buffer) { buffer = null; List 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; } /// 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; } /// public ErrorNumber SubTrackLength(uint head, ushort track, out byte length) { length = 0; List 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 /// public ErrorNumber ReadMediaTag(MediaTagType tag, out byte[] buffer) => throw new NotImplementedException(); /// public ErrorNumber ReadSector(ulong sectorAddress, out byte[] buffer) => throw new NotImplementedException(); /// public ErrorNumber ReadSectorLong(ulong sectorAddress, out byte[] buffer) => throw new NotImplementedException(); /// public ErrorNumber ReadSectors(ulong sectorAddress, uint length, out byte[] buffer) => throw new NotImplementedException(); /// public ErrorNumber ReadSectorsLong(ulong sectorAddress, uint length, out byte[] buffer) => throw new NotImplementedException(); /// public ErrorNumber ReadSectorsTag(ulong sectorAddress, uint length, SectorTagType tag, out byte[] buffer) => throw new NotImplementedException(); /// 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]; } }