From 3dd216608ef3feaddb35d6472a77f53f76b52e10 Mon Sep 17 00:00:00 2001 From: Natalia Portillo Date: Tue, 21 Apr 2026 11:05:34 +0100 Subject: [PATCH] Add support for Software Pirates SNATCH-IT disk images --- .../Localization/Localization.Designer.cs | 12 + Aaru.Images/Localization/Localization.es.resx | 6 + Aaru.Images/Localization/Localization.resx | 6 + Aaru.Images/SnatchIt/Constants.cs | 61 +++ Aaru.Images/SnatchIt/Identify.cs | 75 ++++ Aaru.Images/SnatchIt/Properties.cs | 70 +++ Aaru.Images/SnatchIt/Read.cs | 416 ++++++++++++++++++ Aaru.Images/SnatchIt/SnatchIt.cs | 75 ++++ Aaru.Images/SnatchIt/Structs.cs | 163 +++++++ Aaru.Images/SnatchIt/Unsupported.cs | 89 ++++ README.md | 1 + 11 files changed, 974 insertions(+) create mode 100644 Aaru.Images/SnatchIt/Constants.cs create mode 100644 Aaru.Images/SnatchIt/Identify.cs create mode 100644 Aaru.Images/SnatchIt/Properties.cs create mode 100644 Aaru.Images/SnatchIt/Read.cs create mode 100644 Aaru.Images/SnatchIt/SnatchIt.cs create mode 100644 Aaru.Images/SnatchIt/Structs.cs create mode 100644 Aaru.Images/SnatchIt/Unsupported.cs diff --git a/Aaru.Images/Localization/Localization.Designer.cs b/Aaru.Images/Localization/Localization.Designer.cs index 254fad829..cd70f33ab 100644 --- a/Aaru.Images/Localization/Localization.Designer.cs +++ b/Aaru.Images/Localization/Localization.Designer.cs @@ -5061,6 +5061,18 @@ namespace Aaru.Images { } } + internal static string SnatchIt_Name { + get { + return ResourceManager.GetString("SnatchIt_Name", resourceCulture); + } + } + + internal static string SnatchIt_image_contains_a_disk_of_type_0 { + get { + return ResourceManager.GetString("SnatchIt_image_contains_a_disk_of_type_0", resourceCulture); + } + } + internal static string SuperCardPro_Name { get { return ResourceManager.GetString("SuperCardPro_Name", resourceCulture); diff --git a/Aaru.Images/Localization/Localization.es.resx b/Aaru.Images/Localization/Localization.es.resx index 868a4352f..b7bd5c975 100644 --- a/Aaru.Images/Localization/Localization.es.resx +++ b/Aaru.Images/Localization/Localization.es.resx @@ -2287,6 +2287,12 @@ Guardar sólo sectores únicos. Esta opción consume más memoria y es más lenta, pero está activada por defecto. + + + Software Pirates SNATCH-IT + + + La imagen SNATCH-IT contiene un disco de tipo {0} SuperCardPro diff --git a/Aaru.Images/Localization/Localization.resx b/Aaru.Images/Localization/Localization.resx index 0a8960666..4557f04d4 100644 --- a/Aaru.Images/Localization/Localization.resx +++ b/Aaru.Images/Localization/Localization.resx @@ -2540,6 +2540,12 @@ Compressed SaveDskF images are not supported. + + + Software Pirates SNATCH-IT + + + SNATCH-IT image contains a disk of type {0} SuperCardPro diff --git a/Aaru.Images/SnatchIt/Constants.cs b/Aaru.Images/SnatchIt/Constants.cs new file mode 100644 index 000000000..f8f3c8288 --- /dev/null +++ b/Aaru.Images/SnatchIt/Constants.cs @@ -0,0 +1,61 @@ +// /*************************************************************************** +// Aaru Data Preservation Suite +// ---------------------------------------------------------------------------- +// +// Filename : Constants.cs +// Author(s) : Natalia Portillo +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains constants for Software Pirates SNATCH-IT disk images. +// +// Based on the work of Michal Necasek (fdimg). +// +// --[ 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-2026 Natalia Portillo +// ****************************************************************************/ + +namespace Aaru.Images; + +public sealed partial class SnatchIt +{ + const string MODULE_NAME = "SNATCH-IT plugin"; + + /// Image signature string. + const string SOFTWARE_PIRATES = "SOFTWARE PIRATES"; + /// Version string prefix. + const string RELEASE_PREFIX = "Release"; + + /// Upper bound on the number of cylinders represented per side. + const int NCYL_MAX = 84; + /// Maximum sectors per track supported by the CP2 format. + const int CP2_MAX_SPT = 24; + /// Magic data offset of the first sector in each segment. + const ushort CP2_SEC_OFS_MAGIC = 0x16AD; + /// Trailer byte value marking the last segment. + const byte CP2_TRAILER_LAST = 0xFF; + /// uPD765 ST2 control-mark bit (deleted Data Address Mark). + const byte CP2_ST2_DELETED_DAM = 0x40; + + /// Placeholder text returned for an entirely missing track. + const string CP2_TRACK_MISSING_TEXT = "CP2 track missing!"; + /// Placeholder text returned for an individual missing sector. + const string CP2_SECTOR_MISSING_TEXT = "CP2 sector missing!"; +} \ No newline at end of file diff --git a/Aaru.Images/SnatchIt/Identify.cs b/Aaru.Images/SnatchIt/Identify.cs new file mode 100644 index 000000000..7c9dfa88b --- /dev/null +++ b/Aaru.Images/SnatchIt/Identify.cs @@ -0,0 +1,75 @@ +// /*************************************************************************** +// Aaru Data Preservation Suite +// ---------------------------------------------------------------------------- +// +// Filename : Identify.cs +// Author(s) : Natalia Portillo +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Identifies Software Pirates SNATCH-IT disk images. +// +// Based on the work of Michal Necasek (fdimg). +// +// --[ 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-2026 Natalia Portillo +// ****************************************************************************/ + +using System.IO; +using System.Text; +using Aaru.CommonTypes.Interfaces; +using Aaru.Helpers; + +namespace Aaru.Images; + +public sealed partial class SnatchIt +{ +#region IMediaImage Members + + /// + public bool Identify(IFilter imageFilter) + { + Stream stream = imageFilter.GetDataForkStream(); + stream.Seek(0, SeekOrigin.Begin); + + // Header is 30 bytes. + if(stream.Length < 30) return false; + + var hdr = new byte[30]; + + if(stream.EnsureRead(hdr, 0, 30) != 30) return false; + + string sig = Encoding.ASCII.GetString(hdr, 0, 16); + + if(sig != SOFTWARE_PIRATES) return false; + + string ver = Encoding.ASCII.GetString(hdr, 16, 7); + + if(ver != RELEASE_PREFIX) return false; + + // Reject multi-volume split images; only volume 0 is a self-contained image. + if(hdr[28] != (byte)'$') return false; + if(hdr[29] != (byte)'0') return false; + + return true; + } + +#endregion +} \ No newline at end of file diff --git a/Aaru.Images/SnatchIt/Properties.cs b/Aaru.Images/SnatchIt/Properties.cs new file mode 100644 index 000000000..36659b3fb --- /dev/null +++ b/Aaru.Images/SnatchIt/Properties.cs @@ -0,0 +1,70 @@ +// /*************************************************************************** +// Aaru Data Preservation Suite +// ---------------------------------------------------------------------------- +// +// Filename : Properties.cs +// Author(s) : Natalia Portillo +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains properties for Software Pirates SNATCH-IT disk images. +// +// Based on the work of Michal Necasek (fdimg). +// +// --[ 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-2026 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using Aaru.CommonTypes.AaruMetadata; +using Aaru.CommonTypes.Structs; + +namespace Aaru.Images; + +public sealed partial class SnatchIt +{ +#region IMediaImage Members + + /// + + // ReSharper disable once ConvertToAutoProperty + public ImageInfo Info => _imageInfo; + + /// + public string Name => Localization.SnatchIt_Name; + + /// + public Guid Id => new("B6B2F6C1-5D8E-4C6A-9D4F-2F7E8B9A1C3D"); + + /// + public string Format => "Software Pirates SNATCH-IT"; + + /// + public string Author => Authors.NataliaPortillo; + + /// + public List DumpHardware => null; + + /// + public Metadata AaruMetadata => null; + +#endregion +} \ No newline at end of file diff --git a/Aaru.Images/SnatchIt/Read.cs b/Aaru.Images/SnatchIt/Read.cs new file mode 100644 index 000000000..da9e7ae8c --- /dev/null +++ b/Aaru.Images/SnatchIt/Read.cs @@ -0,0 +1,416 @@ +// /*************************************************************************** +// Aaru Data Preservation Suite +// ---------------------------------------------------------------------------- +// +// Filename : Read.cs +// Author(s) : Natalia Portillo +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Reads Software Pirates SNATCH-IT disk images. +// +// Based on the work of Michal Necasek (fdimg). +// +// --[ 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-2026 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.IO; +using System.Linq; +using System.Text; +using Aaru.CommonTypes; +using Aaru.CommonTypes.Enums; +using Aaru.CommonTypes.Interfaces; +using Aaru.Helpers; +using Aaru.Logging; + +namespace Aaru.Images; + +public sealed partial class SnatchIt +{ + /// Size in bytes of the on-disk track header block. + const int CP2_TRACK_HEADER_SIZE = 3 + CP2_MAX_SPT * 16; + + /// Convert size code to bytes (128 << code). + static uint SectorSizeFromCode(byte code) => (uint)(128 << code); + + /// Walk all segments from and populate . + ErrorNumber BuildTrackMap(long startOfs, out byte heads, out byte cyls) + { + heads = 1; + cyls = 0; + + long currOfs = startOfs; + var cylLast = 0; + var seen2ndSide = false; + var blkSzBuf = new byte[2]; + var trailerBuf = new byte[1]; + var thdrBuf = new byte[CP2_TRACK_HEADER_SIZE]; + + _stream.Seek(currOfs, SeekOrigin.Begin); + + for(;;) + { + var firstInSeg = true; + + if(_stream.EnsureRead(blkSzBuf, 0, 2) != 2) break; + + var blkSz = (ushort)(blkSzBuf[0] | blkSzBuf[1] << 8); + + if(blkSz == 0 || blkSz >= 0x8000) return ErrorNumber.InvalidArgument; + + if((blkSz - 1) % CP2_TRACK_HEADER_SIZE != 0) return ErrorNumber.InvalidArgument; + + currOfs += 2; + + long trkDataOfs = currOfs + blkSz + 2; + int nThdrs = (blkSz - 1) / CP2_TRACK_HEADER_SIZE; + + for(var i = 0; i < nThdrs; i++) + { + if(_stream.EnsureRead(thdrBuf, 0, CP2_TRACK_HEADER_SIZE) != CP2_TRACK_HEADER_SIZE) + return ErrorNumber.InOutError; + + currOfs += CP2_TRACK_HEADER_SIZE; + + byte cyl = thdrBuf[0]; + byte head = thdrBuf[1]; + byte numSect = thdrBuf[2]; + var phantom = false; + + if(numSect == 1) + { + // First sector header starts at offset 3; fields inside: st0..st2 at 1..3, hdr_cyl at 4, hdr_sec at 6, size_code at 7 + byte pcyl = thdrBuf[3 + 4]; + byte psec = thdrBuf[3 + 6]; + byte pcode = thdrBuf[3 + 7]; + + if(pcyl == 6 && psec == 6 && pcode == 6) phantom = true; + } + + if(phantom) + { + firstInSeg = false; + + continue; + } + + ErrorNumber rc = MapTrack(thdrBuf, cyl, head, numSect, trkDataOfs, firstInSeg); + + if(rc != ErrorNumber.NoError) return rc; + + if(cyl > cylLast) cylLast = cyl; + + if(head != 0) seen2ndSide = true; + + firstInSeg = false; + } + + if(_stream.EnsureRead(trailerBuf, 0, 1) != 1) return ErrorNumber.InOutError; + + byte trailer = trailerBuf[0]; + + currOfs += 1; + + if(_stream.EnsureRead(blkSzBuf, 0, 2) != 2) return ErrorNumber.InOutError; + + var dataBlkSz = (ushort)(blkSzBuf[0] | blkSzBuf[1] << 8); + + currOfs += 2; + + currOfs += dataBlkSz; + + _stream.Seek(currOfs, SeekOrigin.Begin); + + if(trailer == CP2_TRAILER_LAST) break; + } + + heads = (byte)(seen2ndSide ? 2 : 1); + cyls = (byte)(cylLast + 1); + + return ErrorNumber.NoError; + } + + /// Parse sector headers from a track header block and store descriptors into . + ErrorNumber MapTrack(byte[] thdrBuf, byte cyl, byte head, byte numSect, long trkDataOfs, bool firstInSeg) + { + if(cyl >= NCYL_MAX) return ErrorNumber.InvalidArgument; + + if(head > 1) return ErrorNumber.InvalidArgument; + + int trkIdx = cyl * 2 + head; + + if(trkIdx >= _trackMap.Length) return ErrorNumber.InvalidArgument; + + if(_trackMap[trkIdx] is not null) return ErrorNumber.InvalidArgument; + + var sectors = new SectorDesc[numSect]; + + for(var ps = 0; ps < numSect; ps++) + { + int off = 3 + ps * 16; + byte result = thdrBuf[off + 0]; + byte st0 = thdrBuf[off + 1]; + byte st1 = thdrBuf[off + 2]; + byte st2 = thdrBuf[off + 3]; + byte hdrCyl = thdrBuf[off + 4]; + byte hdrHead = thdrBuf[off + 5]; + byte hdrSec = thdrBuf[off + 6]; + byte sizCode = thdrBuf[off + 7]; + byte ofsLo = thdrBuf[off + 8]; + byte ofsHi = thdrBuf[off + 9]; + + var secOfs = (ushort)(ofsHi << 8 | ofsLo); + + if(ps == 0 && firstInSeg && secOfs != CP2_SEC_OFS_MAGIC) return ErrorNumber.InvalidArgument; + + if(secOfs < CP2_SEC_OFS_MAGIC) return ErrorNumber.InvalidArgument; + + // ST0 bits 0:1 = drive select, bit 2 = head; other bits indicate abnormal termination or error. + // ST1/ST2 nonzero generally indicate errors. The ST2 Control Mark bit marks a Deleted Data + // Address Mark, which is not an error per se but is tracked separately. Any other abnormal bit + // set leaves the sector flagged as errored rather than rejecting the whole image. + bool errored = (st0 & 0xF8) != 0 || st1 != 0 || (st2 & ~CP2_ST2_DELETED_DAM) != 0; + + if(hdrCyl != cyl) return ErrorNumber.InvalidArgument; + + if(hdrHead != head) return ErrorNumber.InvalidArgument; + + uint secSize = SectorSizeFromCode(sizCode); + + sectors[ps] = new SectorDesc + { + FileOffset = trkDataOfs + secOfs - CP2_SEC_OFS_MAGIC, + Size = secSize, + SectorId = hdrSec, + Deleted = (st2 & CP2_ST2_DELETED_DAM) != 0, + Errored = errored + }; + + _ = result; + } + + _trackMap[trkIdx] = new TrackDesc + { + Sectors = sectors + }; + + return ErrorNumber.NoError; + } + +#region IMediaImage Members + + /// + public ErrorNumber Open(IFilter imageFilter) + { + _stream = imageFilter.GetDataForkStream(); + _stream.Seek(0, SeekOrigin.Begin); + + var hdrBuf = new byte[30]; + + if(_stream.EnsureRead(hdrBuf, 0, 30) != 30) return ErrorNumber.InvalidArgument; + + string sig = Encoding.ASCII.GetString(hdrBuf, 0, 16); + + if(sig != SOFTWARE_PIRATES) return ErrorNumber.InvalidArgument; + + string ver = Encoding.ASCII.GetString(hdrBuf, 16, 7); + + if(ver != RELEASE_PREFIX) return ErrorNumber.InvalidArgument; + + if(hdrBuf[28] != (byte)'$' || hdrBuf[29] != (byte)'0') return ErrorNumber.NotSupported; + + string version = Encoding.ASCII.GetString(hdrBuf, 16, 12).TrimEnd('\0', ' '); + + AaruLogging.Debug(MODULE_NAME, "header.version = {0}", version); + + _trackMap = new TrackDesc[NCYL_MAX * 2]; + + ErrorNumber rc = BuildTrackMap(30, out byte heads, out byte cyls); + + if(rc != ErrorNumber.NoError) return rc; + + if(cyls == 0) return ErrorNumber.InvalidArgument; + + // First recorded track is always trk_ofs[0] (cyl 0, head 0) for a sane image. + TrackDesc firstTrack = _trackMap[0]; + + if(firstTrack?.Sectors is null || firstTrack.Sectors.Length == 0) return ErrorNumber.InvalidArgument; + + byte firstId = 255; + + foreach (SectorDesc sd in firstTrack.Sectors.Where(sd => sd.SectorId < firstId)) + firstId = sd.SectorId; + + var spt = (byte)firstTrack.Sectors.Length; + + AaruLogging.Debug(MODULE_NAME, "cyls = {0}, heads = {1}, spt = {2}, firstId = {3}", cyls, heads, spt, firstId); + + ulong totalSectors = (ulong)cyls * heads * spt; + + _sectorMap = new SectorLoc[totalSectors]; + + uint maxSectorSize = 0; + + for(ulong lba = 0; lba < totalSectors; lba++) + { + var track = (int)(lba / spt); + + if(heads == 1) track *= 2; + + int logicalId = (int)(lba % spt) + firstId; + + TrackDesc td = track < _trackMap.Length ? _trackMap[track] : null; + + if(td?.Sectors is null) + { + _sectorMap[lba] = new SectorLoc + { + TrackPresent = false, + SectorPresent = false, + Size = 0, + FileOffset = -1 + }; + + continue; + } + + SectorDesc found = td.Sectors.FirstOrDefault(sd => sd.SectorId == logicalId); + + if(found is null) + { + _sectorMap[lba] = new SectorLoc + { + TrackPresent = true, + SectorPresent = false, + Size = 0, + FileOffset = -1 + }; + + continue; + } + + if(found.Size > maxSectorSize) maxSectorSize = found.Size; + + _sectorMap[lba] = new SectorLoc + { + TrackPresent = true, + SectorPresent = true, + FileOffset = found.FileOffset, + Size = found.Size, + Deleted = found.Deleted, + Errored = found.Errored + }; + } + + if(maxSectorSize == 0) maxSectorSize = 512; + + _imageInfo.MetadataMediaType = MetadataMediaType.BlockMedia; + _imageInfo.Sectors = totalSectors; + _imageInfo.SectorSize = maxSectorSize; + _imageInfo.ImageSize = (ulong)_stream.Length - 30; + _imageInfo.Heads = heads; + _imageInfo.Cylinders = cyls; + _imageInfo.SectorsPerTrack = spt; + _imageInfo.Application = "SNATCH-IT"; + _imageInfo.Version = version; + _imageInfo.CreationTime = imageFilter.CreationTime; + _imageInfo.LastModificationTime = imageFilter.LastWriteTime; + + _imageInfo.MediaType = Geometry.GetMediaType((cyls, heads, spt, maxSectorSize, MediaEncoding.MFM, false)); + + AaruLogging.Verbose(Localization.SnatchIt_image_contains_a_disk_of_type_0, _imageInfo.MediaType); + + return ErrorNumber.NoError; + } + + /// + public ErrorNumber ReadSector(ulong sectorAddress, bool negative, out byte[] buffer, out SectorStatus sectorStatus) + { + ErrorNumber rc = ReadSectors(sectorAddress, negative, 1, out buffer, out SectorStatus[] statuses); + + sectorStatus = statuses is { Length: > 0 } ? statuses[0] : SectorStatus.NotDumped; + + return rc; + } + + /// + public ErrorNumber ReadSectors(ulong sectorAddress, bool negative, uint length, out byte[] buffer, + out SectorStatus[] sectorStatus) + { + buffer = null; + sectorStatus = null; + + if(negative) return ErrorNumber.NotSupported; + + if(sectorAddress >= _imageInfo.Sectors) return ErrorNumber.OutOfRange; + + if(sectorAddress + length > _imageInfo.Sectors) return ErrorNumber.OutOfRange; + + // Pass 1: compute the concatenated output length (sum of each sector's real size). + long totalLen = 0; + + for(uint i = 0; i < length; i++) + { + SectorLoc loc = _sectorMap[sectorAddress + i]; + + totalLen += loc.SectorPresent ? loc.Size : _imageInfo.SectorSize; + } + + buffer = new byte[totalLen]; + sectorStatus = new SectorStatus[length]; + + long offset = 0; + + for(uint i = 0; i < length; i++) + { + SectorLoc loc = _sectorMap[sectorAddress + i]; + + if(!loc.SectorPresent) + { + string placeholder = !loc.TrackPresent ? CP2_TRACK_MISSING_TEXT : CP2_SECTOR_MISSING_TEXT; + + byte[] txt = Encoding.ASCII.GetBytes(placeholder); + + Array.Copy(txt, 0, buffer, offset, Math.Min(txt.Length, _imageInfo.SectorSize)); + + offset += _imageInfo.SectorSize; + sectorStatus[i] = SectorStatus.NotDumped; + + continue; + } + + _stream.Seek(loc.FileOffset, SeekOrigin.Begin); + + if(_stream.EnsureRead(buffer, (int)offset, (int)loc.Size) != (int)loc.Size) return ErrorNumber.InOutError; + + offset += loc.Size; + + // SectorStatus has no dedicated "deleted DAM" value; map both deleted-DAM and FDC error + // bits to Errored. Otherwise the sector was read cleanly. + sectorStatus[i] = loc.Deleted || loc.Errored ? SectorStatus.Errored : SectorStatus.Dumped; + } + + return ErrorNumber.NoError; + } + +#endregion +} \ No newline at end of file diff --git a/Aaru.Images/SnatchIt/SnatchIt.cs b/Aaru.Images/SnatchIt/SnatchIt.cs new file mode 100644 index 000000000..df532e269 --- /dev/null +++ b/Aaru.Images/SnatchIt/SnatchIt.cs @@ -0,0 +1,75 @@ +// /*************************************************************************** +// Aaru Data Preservation Suite +// ---------------------------------------------------------------------------- +// +// Filename : SnatchIt.cs +// Author(s) : Natalia Portillo +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Manages Software Pirates SNATCH-IT / Central Point COPY II-PC disk +// images. +// +// Based on the work of Michal Necasek (fdimg). +// +// --[ 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-2026 Natalia Portillo +// ****************************************************************************/ + +using System.IO; +using Aaru.CommonTypes.Interfaces; +using Aaru.CommonTypes.Structs; + +namespace Aaru.Images; + +/// +/// Implements reading Software Pirates SNATCH-IT / Central Point COPY II-PC disk images +public sealed partial class SnatchIt : IMediaImage +{ + Cp2Header _header; + ImageInfo _imageInfo; + SectorLoc[] _sectorMap; + Stream _stream; + TrackDesc[] _trackMap; + + public SnatchIt() => _imageInfo = new ImageInfo + { + ReadableSectorTags = [], + ReadableMediaTags = [], + 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 + }; +} \ No newline at end of file diff --git a/Aaru.Images/SnatchIt/Structs.cs b/Aaru.Images/SnatchIt/Structs.cs new file mode 100644 index 000000000..2c68e6deb --- /dev/null +++ b/Aaru.Images/SnatchIt/Structs.cs @@ -0,0 +1,163 @@ +// /*************************************************************************** +// Aaru Data Preservation Suite +// ---------------------------------------------------------------------------- +// +// Filename : Structs.cs +// Author(s) : Natalia Portillo +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains structures for Software Pirates SNATCH-IT disk images. +// +// Based on the work of Michal Necasek (fdimg). +// +// --[ 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-2026 Natalia Portillo +// ****************************************************************************/ + +using System.Runtime.InteropServices; + +namespace Aaru.Images; + +public sealed partial class SnatchIt +{ +#region Nested type: Cp2Header + + /// On-disk SNATCH-IT image header (30 bytes). + [StructLayout(LayoutKind.Sequential, Pack = 1)] + readonly struct Cp2Header + { + /// 'SOFTWARE PIRATES' signature. + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] + public readonly byte[] pirates; + /// Release number string. + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 12)] + public readonly byte[] version; + /// '$' literal. + public readonly byte dollar; + /// ASCII digit, volume number. + public readonly byte vol_no; + } + +#endregion + +#region Nested type: Cp2SectorHeader + + /// On-disk SNATCH-IT sector header (16 bytes). + [StructLayout(LayoutKind.Sequential, Pack = 1)] + readonly struct Cp2SectorHeader + { + /// FDC read result (normally 0). + public readonly byte result; + /// Status ST0. + public readonly byte st0; + /// Status ST1. + public readonly byte st1; + /// Status ST2. + public readonly byte st2; + /// Cylinder number from ID. + public readonly byte hdr_cyl; + /// Head number from ID. + public readonly byte hdr_head; + /// Sector number from ID. + public readonly byte hdr_sec; + /// Sector size code. + public readonly byte size_code; + /// Sector offset low byte. + public readonly byte sofs_lo; + /// Sector offset high byte. + public readonly byte sofs_hi; + /// Unknown/reserved. + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)] + public readonly byte[] unk; + } + +#endregion + +#region Nested type: Cp2TrackHeader + + /// On-disk SNATCH-IT track header (3 + 24 * 16 = 387 bytes). + [StructLayout(LayoutKind.Sequential, Pack = 1)] + readonly struct Cp2TrackHeader + { + /// Physical cylinder (zero-based). + public readonly byte cyl; + /// Physical head number. + public readonly byte head; + /// Number of sectors on this track. + public readonly byte num_sect; + /// Sector headers. + [MarshalAs(UnmanagedType.ByValArray, SizeConst = CP2_MAX_SPT)] + public readonly Cp2SectorHeader[] sct_hdr; + } + +#endregion + +#region Nested type: SectorDesc + + /// In-memory descriptor for a single recorded sector. + sealed class SectorDesc + { + /// True if the sector was recorded with a Deleted Data Address Mark. + public bool Deleted; + /// True if the sector was recorded with any uPD765 ST0/ST1/ST2 error bits set. + public bool Errored; + /// Offset of sector data in the image file. + public long FileOffset; + /// Logical sector ID from the ID AM. + public byte SectorId; + /// Sector size in bytes (may differ from siblings in the same track). + public uint Size; + } + +#endregion + +#region Nested type: TrackDesc + + /// In-memory descriptor for a single track. + sealed class TrackDesc + { + /// Recorded sectors for this track, in physical order. + public SectorDesc[] Sectors; + } + +#endregion + +#region Nested type: SectorLoc + + /// Per-LBA map entry used by the reader. + struct SectorLoc + { + /// Offset of sector data in the image file. + public long FileOffset; + /// Native sector size in bytes. + public uint Size; + /// Whether the track containing this LBA was present in the image. + public bool TrackPresent; + /// Whether this LBA's sector ID was found within its track. + public bool SectorPresent; + /// Whether the sector was recorded with a Deleted Data Address Mark. + public bool Deleted; + /// Whether the sector was recorded with any uPD765 ST0/ST1/ST2 error bits set. + public bool Errored; + } + +#endregion +} \ No newline at end of file diff --git a/Aaru.Images/SnatchIt/Unsupported.cs b/Aaru.Images/SnatchIt/Unsupported.cs new file mode 100644 index 000000000..bab0b6e0a --- /dev/null +++ b/Aaru.Images/SnatchIt/Unsupported.cs @@ -0,0 +1,89 @@ +// /*************************************************************************** +// Aaru Data Preservation Suite +// ---------------------------------------------------------------------------- +// +// Filename : Unsupported.cs +// Author(s) : Natalia Portillo +// +// Component : Disk image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Contains features unsupported by Software Pirates SNATCH-IT disk images. +// +// Based on the work of Michal Necasek (fdimg). +// +// --[ 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-2026 Natalia Portillo +// ****************************************************************************/ + +using Aaru.CommonTypes.Enums; + +namespace Aaru.Images; + +public sealed partial class SnatchIt +{ +#region IMediaImage Members + + /// + public ErrorNumber ReadSectorTag(ulong sectorAddress, bool negative, SectorTagType tag, out byte[] buffer) + { + buffer = null; + + return ErrorNumber.NotSupported; + } + + /// + public ErrorNumber ReadSectorsTag(ulong sectorAddress, bool negative, uint length, SectorTagType tag, + out byte[] buffer) + { + buffer = null; + + return ErrorNumber.NotSupported; + } + + /// + public ErrorNumber ReadSectorLong(ulong sectorAddress, bool negative, out byte[] buffer, + out SectorStatus sectorStatus) + { + buffer = null; + sectorStatus = SectorStatus.NotDumped; + + return ErrorNumber.NotSupported; + } + + /// + public ErrorNumber ReadSectorsLong(ulong sectorAddress, bool negative, uint length, out byte[] buffer, + out SectorStatus[] sectorStatus) + { + buffer = null; + sectorStatus = null; + + return ErrorNumber.NotSupported; + } + + /// + public ErrorNumber ReadMediaTag(MediaTagType tag, out byte[] buffer) + { + buffer = null; + + return ErrorNumber.NotSupported; + } + +#endregion +} \ No newline at end of file diff --git a/README.md b/README.md index 3c609ec4b..dd48bc6eb 100644 --- a/README.md +++ b/README.md @@ -110,6 +110,7 @@ Media image formats we can read * Partimage disk images * PowerISO (.DAA/.GBI) * Quasi88 disk images (.D77/.D88) +* Software Pirates SNATCH-IT / Central Point COPY II-PC (.CP2) * Spectrum floppy disk image (.FDI) * SuperCardPro * TeleDisk