Add support for Software Pirates SNATCH-IT disk images

This commit is contained in:
2026-04-21 11:05:34 +01:00
parent b1b0b3b262
commit 3dd216608e
11 changed files with 974 additions and 0 deletions

View File

@@ -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);

View File

@@ -2287,6 +2287,12 @@
<data name="Store_only_unique_sectors_This_consumes_more_memory_and_is_slower_but_its_enabled_by_default"
xml:space="preserve">
<value>Guardar sólo sectores únicos. Esta opción consume más memoria y es más lenta, pero está activada por defecto.</value>
</data>
<data name="SnatchIt_Name" xml:space="preserve">
<value>Software Pirates SNATCH-IT</value>
</data>
<data name="SnatchIt_image_contains_a_disk_of_type_0" xml:space="preserve">
<value>La imagen SNATCH-IT contiene un disco de tipo {0}</value>
</data>
<data name="SuperCardPro_Name" xml:space="preserve">
<value>SuperCardPro</value>

View File

@@ -2540,6 +2540,12 @@
</data>
<data name="Compressed_SaveDskF_images_are_not_supported" xml:space="preserve">
<value>Compressed SaveDskF images are not supported.</value>
</data>
<data name="SnatchIt_Name" xml:space="preserve">
<value>Software Pirates SNATCH-IT</value>
</data>
<data name="SnatchIt_image_contains_a_disk_of_type_0" xml:space="preserve">
<value>SNATCH-IT image contains a disk of type {0}</value>
</data>
<data name="SuperCardPro_Name" xml:space="preserve">
<value>SuperCardPro</value>

View File

@@ -0,0 +1,61 @@
// /***************************************************************************
// Aaru Data Preservation Suite
// ----------------------------------------------------------------------------
//
// Filename : Constants.cs
// Author(s) : Natalia Portillo <claunia@claunia.com>
//
// 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 <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------
// Copyright © 2011-2026 Natalia Portillo
// ****************************************************************************/
namespace Aaru.Images;
public sealed partial class SnatchIt
{
const string MODULE_NAME = "SNATCH-IT plugin";
/// <summary>Image signature string.</summary>
const string SOFTWARE_PIRATES = "SOFTWARE PIRATES";
/// <summary>Version string prefix.</summary>
const string RELEASE_PREFIX = "Release";
/// <summary>Upper bound on the number of cylinders represented per side.</summary>
const int NCYL_MAX = 84;
/// <summary>Maximum sectors per track supported by the CP2 format.</summary>
const int CP2_MAX_SPT = 24;
/// <summary>Magic data offset of the first sector in each segment.</summary>
const ushort CP2_SEC_OFS_MAGIC = 0x16AD;
/// <summary>Trailer byte value marking the last segment.</summary>
const byte CP2_TRAILER_LAST = 0xFF;
/// <summary>uPD765 ST2 control-mark bit (deleted Data Address Mark).</summary>
const byte CP2_ST2_DELETED_DAM = 0x40;
/// <summary>Placeholder text returned for an entirely missing track.</summary>
const string CP2_TRACK_MISSING_TEXT = "CP2 track missing!";
/// <summary>Placeholder text returned for an individual missing sector.</summary>
const string CP2_SECTOR_MISSING_TEXT = "CP2 sector missing!";
}

View File

@@ -0,0 +1,75 @@
// /***************************************************************************
// Aaru Data Preservation Suite
// ----------------------------------------------------------------------------
//
// Filename : Identify.cs
// Author(s) : Natalia Portillo <claunia@claunia.com>
//
// 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 <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------
// 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
/// <inheritdoc />
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
}

View File

@@ -0,0 +1,70 @@
// /***************************************************************************
// Aaru Data Preservation Suite
// ----------------------------------------------------------------------------
//
// Filename : Properties.cs
// Author(s) : Natalia Portillo <claunia@claunia.com>
//
// 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 <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------
// 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
/// <inheritdoc />
// ReSharper disable once ConvertToAutoProperty
public ImageInfo Info => _imageInfo;
/// <inheritdoc />
public string Name => Localization.SnatchIt_Name;
/// <inheritdoc />
public Guid Id => new("B6B2F6C1-5D8E-4C6A-9D4F-2F7E8B9A1C3D");
/// <inheritdoc />
public string Format => "Software Pirates SNATCH-IT";
/// <inheritdoc />
public string Author => Authors.NataliaPortillo;
/// <inheritdoc />
public List<DumpHardware> DumpHardware => null;
/// <inheritdoc />
public Metadata AaruMetadata => null;
#endregion
}

View File

@@ -0,0 +1,416 @@
// /***************************************************************************
// Aaru Data Preservation Suite
// ----------------------------------------------------------------------------
//
// Filename : Read.cs
// Author(s) : Natalia Portillo <claunia@claunia.com>
//
// 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 <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------
// 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
{
/// <summary>Size in bytes of the on-disk track header block.</summary>
const int CP2_TRACK_HEADER_SIZE = 3 + CP2_MAX_SPT * 16;
/// <summary>Convert size code to bytes (128 &lt;&lt; code).</summary>
static uint SectorSizeFromCode(byte code) => (uint)(128 << code);
/// <summary>Walk all segments from <paramref name="startOfs" /> and populate <see cref="_trackMap" />.</summary>
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;
}
/// <summary>Parse sector headers from a track header block and store descriptors into <see cref="_trackMap" />.</summary>
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
/// <inheritdoc />
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;
}
/// <inheritdoc />
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;
}
/// <inheritdoc />
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
}

View File

@@ -0,0 +1,75 @@
// /***************************************************************************
// Aaru Data Preservation Suite
// ----------------------------------------------------------------------------
//
// Filename : SnatchIt.cs
// Author(s) : Natalia Portillo <claunia@claunia.com>
//
// 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 <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------
// Copyright © 2011-2026 Natalia Portillo
// ****************************************************************************/
using System.IO;
using Aaru.CommonTypes.Interfaces;
using Aaru.CommonTypes.Structs;
namespace Aaru.Images;
/// <inheritdoc cref="Aaru.CommonTypes.Interfaces.IMediaImage" />
/// <summary>Implements reading Software Pirates SNATCH-IT / Central Point COPY II-PC disk images</summary>
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
};
}

View File

@@ -0,0 +1,163 @@
// /***************************************************************************
// Aaru Data Preservation Suite
// ----------------------------------------------------------------------------
//
// Filename : Structs.cs
// Author(s) : Natalia Portillo <claunia@claunia.com>
//
// 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 <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------
// Copyright © 2011-2026 Natalia Portillo
// ****************************************************************************/
using System.Runtime.InteropServices;
namespace Aaru.Images;
public sealed partial class SnatchIt
{
#region Nested type: Cp2Header
/// <summary>On-disk SNATCH-IT image header (30 bytes).</summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
readonly struct Cp2Header
{
/// <summary>'SOFTWARE PIRATES' signature.</summary>
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
public readonly byte[] pirates;
/// <summary>Release number string.</summary>
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 12)]
public readonly byte[] version;
/// <summary>'$' literal.</summary>
public readonly byte dollar;
/// <summary>ASCII digit, volume number.</summary>
public readonly byte vol_no;
}
#endregion
#region Nested type: Cp2SectorHeader
/// <summary>On-disk SNATCH-IT sector header (16 bytes).</summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
readonly struct Cp2SectorHeader
{
/// <summary>FDC read result (normally 0).</summary>
public readonly byte result;
/// <summary>Status ST0.</summary>
public readonly byte st0;
/// <summary>Status ST1.</summary>
public readonly byte st1;
/// <summary>Status ST2.</summary>
public readonly byte st2;
/// <summary>Cylinder number from ID.</summary>
public readonly byte hdr_cyl;
/// <summary>Head number from ID.</summary>
public readonly byte hdr_head;
/// <summary>Sector number from ID.</summary>
public readonly byte hdr_sec;
/// <summary>Sector size code.</summary>
public readonly byte size_code;
/// <summary>Sector offset low byte.</summary>
public readonly byte sofs_lo;
/// <summary>Sector offset high byte.</summary>
public readonly byte sofs_hi;
/// <summary>Unknown/reserved.</summary>
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
public readonly byte[] unk;
}
#endregion
#region Nested type: Cp2TrackHeader
/// <summary>On-disk SNATCH-IT track header (3 + 24 * 16 = 387 bytes).</summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
readonly struct Cp2TrackHeader
{
/// <summary>Physical cylinder (zero-based).</summary>
public readonly byte cyl;
/// <summary>Physical head number.</summary>
public readonly byte head;
/// <summary>Number of sectors on this track.</summary>
public readonly byte num_sect;
/// <summary>Sector headers.</summary>
[MarshalAs(UnmanagedType.ByValArray, SizeConst = CP2_MAX_SPT)]
public readonly Cp2SectorHeader[] sct_hdr;
}
#endregion
#region Nested type: SectorDesc
/// <summary>In-memory descriptor for a single recorded sector.</summary>
sealed class SectorDesc
{
/// <summary>True if the sector was recorded with a Deleted Data Address Mark.</summary>
public bool Deleted;
/// <summary>True if the sector was recorded with any uPD765 ST0/ST1/ST2 error bits set.</summary>
public bool Errored;
/// <summary>Offset of sector data in the image file.</summary>
public long FileOffset;
/// <summary>Logical sector ID from the ID AM.</summary>
public byte SectorId;
/// <summary>Sector size in bytes (may differ from siblings in the same track).</summary>
public uint Size;
}
#endregion
#region Nested type: TrackDesc
/// <summary>In-memory descriptor for a single track.</summary>
sealed class TrackDesc
{
/// <summary>Recorded sectors for this track, in physical order.</summary>
public SectorDesc[] Sectors;
}
#endregion
#region Nested type: SectorLoc
/// <summary>Per-LBA map entry used by the reader.</summary>
struct SectorLoc
{
/// <summary>Offset of sector data in the image file.</summary>
public long FileOffset;
/// <summary>Native sector size in bytes.</summary>
public uint Size;
/// <summary>Whether the track containing this LBA was present in the image.</summary>
public bool TrackPresent;
/// <summary>Whether this LBA's sector ID was found within its track.</summary>
public bool SectorPresent;
/// <summary>Whether the sector was recorded with a Deleted Data Address Mark.</summary>
public bool Deleted;
/// <summary>Whether the sector was recorded with any uPD765 ST0/ST1/ST2 error bits set.</summary>
public bool Errored;
}
#endregion
}

View File

@@ -0,0 +1,89 @@
// /***************************************************************************
// Aaru Data Preservation Suite
// ----------------------------------------------------------------------------
//
// Filename : Unsupported.cs
// Author(s) : Natalia Portillo <claunia@claunia.com>
//
// 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 <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------
// Copyright © 2011-2026 Natalia Portillo
// ****************************************************************************/
using Aaru.CommonTypes.Enums;
namespace Aaru.Images;
public sealed partial class SnatchIt
{
#region IMediaImage Members
/// <inheritdoc />
public ErrorNumber ReadSectorTag(ulong sectorAddress, bool negative, SectorTagType tag, out byte[] buffer)
{
buffer = null;
return ErrorNumber.NotSupported;
}
/// <inheritdoc />
public ErrorNumber ReadSectorsTag(ulong sectorAddress, bool negative, uint length, SectorTagType tag,
out byte[] buffer)
{
buffer = null;
return ErrorNumber.NotSupported;
}
/// <inheritdoc />
public ErrorNumber ReadSectorLong(ulong sectorAddress, bool negative, out byte[] buffer,
out SectorStatus sectorStatus)
{
buffer = null;
sectorStatus = SectorStatus.NotDumped;
return ErrorNumber.NotSupported;
}
/// <inheritdoc />
public ErrorNumber ReadSectorsLong(ulong sectorAddress, bool negative, uint length, out byte[] buffer,
out SectorStatus[] sectorStatus)
{
buffer = null;
sectorStatus = null;
return ErrorNumber.NotSupported;
}
/// <inheritdoc />
public ErrorNumber ReadMediaTag(MediaTagType tag, out byte[] buffer)
{
buffer = null;
return ErrorNumber.NotSupported;
}
#endregion
}

View File

@@ -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