Add support for Sydex CopyQM+ Self-eXtracting Disk (SXD) images

This commit is contained in:
2026-04-21 17:46:21 +01:00
parent a477a7f421
commit a69a2a0c2d
14 changed files with 1526 additions and 0 deletions

View File

@@ -3987,6 +3987,72 @@ namespace Aaru.Images {
}
}
internal static string Sxd_Name {
get {
return ResourceManager.GetString("Sxd_Name", resourceCulture);
}
}
internal static string Sxd_header_CRC_mismatch {
get {
return ResourceManager.GetString("Sxd_header_CRC_mismatch", resourceCulture);
}
}
internal static string Sxd_image_is_password_protected_and_unsupported {
get {
return ResourceManager.GetString("Sxd_image_is_password_protected_and_unsupported", resourceCulture);
}
}
internal static string Sxd_image_has_invalid_geometry {
get {
return ResourceManager.GetString("Sxd_image_has_invalid_geometry", resourceCulture);
}
}
internal static string Sxd_inconsistent_track_length {
get {
return ResourceManager.GetString("Sxd_inconsistent_track_length", resourceCulture);
}
}
internal static string Sxd_comment_CRC_mismatch_discarded {
get {
return ResourceManager.GetString("Sxd_comment_CRC_mismatch_discarded", resourceCulture);
}
}
internal static string Sxd_track_decompression_failed_0 {
get {
return ResourceManager.GetString("Sxd_track_decompression_failed_0", resourceCulture);
}
}
internal static string Sxd_track_compressed_length_out_of_range_0 {
get {
return ResourceManager.GetString("Sxd_track_compressed_length_out_of_range_0", resourceCulture);
}
}
internal static string Sxd_image_contains_a_disk_of_type_0 {
get {
return ResourceManager.GetString("Sxd_image_contains_a_disk_of_type_0", resourceCulture);
}
}
internal static string Sxd_comments_0 {
get {
return ResourceManager.GetString("Sxd_comments_0", resourceCulture);
}
}
internal static string Sxd_all_track_CRCs_match_0 {
get {
return ResourceManager.GetString("Sxd_all_track_CRCs_match_0", resourceCulture);
}
}
internal static string Calculated_header_checksum_equals_0_X2_1 {
get {
return ResourceManager.GetString("Calculated_header_checksum_equals_0_X2_1", resourceCulture);

View File

@@ -487,6 +487,39 @@
</data>
<data name="Dxp_comments_0" xml:space="preserve">
<value>Comentarios DXP: {0}</value>
</data>
<data name="Sxd_Name" xml:space="preserve">
<value>Disco Auto-extraíble Sydex CopyQM+</value>
</data>
<data name="Sxd_header_CRC_mismatch" xml:space="preserve">
<value>El CRC de la cabecera SXD no coincide; la imagen está dañada o no es SXD.</value>
</data>
<data name="Sxd_image_is_password_protected_and_unsupported" xml:space="preserve">
<value>La imagen SXD está protegida por contraseña; el descifrado no está soportado.</value>
</data>
<data name="Sxd_image_has_invalid_geometry" xml:space="preserve">
<value>La imagen SXD indica una geometría de disco inválida.</value>
</data>
<data name="Sxd_inconsistent_track_length" xml:space="preserve">
<value>La longitud de pista de la cabecera SXD es inconsistente con los sectores por pista.</value>
</data>
<data name="Sxd_comment_CRC_mismatch_discarded" xml:space="preserve">
<value>El CRC del comentario SXD no coincide; comentario descartado.</value>
</data>
<data name="Sxd_track_decompression_failed_0" xml:space="preserve">
<value>Falló la descompresión de la pista SXD {0}.</value>
</data>
<data name="Sxd_track_compressed_length_out_of_range_0" xml:space="preserve">
<value>La pista SXD tiene una longitud comprimida fuera de rango {0}.</value>
</data>
<data name="Sxd_image_contains_a_disk_of_type_0" xml:space="preserve">
<value>La imagen SXD contiene un disco de tipo {0}</value>
</data>
<data name="Sxd_comments_0" xml:space="preserve">
<value>Comentarios SXD: {0}</value>
</data>
<data name="Sxd_all_track_CRCs_match_0" xml:space="preserve">
<value>CRCs de todas las pistas SXD validados = {0}</value>
</data>
<data name="Copyright_string_0" xml:space="preserve">
<value>Texto de copyright: {0}</value>

View File

@@ -2003,6 +2003,39 @@
</data>
<data name="Dxp_comments_0" xml:space="preserve">
<value>DXP comments: {0}</value>
</data>
<data name="Sxd_Name" xml:space="preserve">
<value>Sydex CopyQM+ Self-eXtracting Disk</value>
</data>
<data name="Sxd_header_CRC_mismatch" xml:space="preserve">
<value>SXD header CRC does not match; image is corrupt or not an SXD image.</value>
</data>
<data name="Sxd_image_is_password_protected_and_unsupported" xml:space="preserve">
<value>SXD image is password protected; decryption is not supported.</value>
</data>
<data name="Sxd_image_has_invalid_geometry" xml:space="preserve">
<value>SXD image reports invalid disk geometry.</value>
</data>
<data name="Sxd_inconsistent_track_length" xml:space="preserve">
<value>SXD header track length is inconsistent with sectors per track.</value>
</data>
<data name="Sxd_comment_CRC_mismatch_discarded" xml:space="preserve">
<value>SXD comment CRC mismatch; comment discarded.</value>
</data>
<data name="Sxd_track_decompression_failed_0" xml:space="preserve">
<value>Decompression failed for SXD track {0}.</value>
</data>
<data name="Sxd_track_compressed_length_out_of_range_0" xml:space="preserve">
<value>SXD track has out-of-range compressed length {0}.</value>
</data>
<data name="Sxd_image_contains_a_disk_of_type_0" xml:space="preserve">
<value>SXD image contains a disk of type {0}</value>
</data>
<data name="Sxd_comments_0" xml:space="preserve">
<value>SXD comments: {0}</value>
</data>
<data name="Sxd_all_track_CRCs_match_0" xml:space="preserve">
<value>All SXD track CRCs validated = {0}</value>
</data>
<data name="Calculated_header_checksum_equals_0_X2_1" xml:space="preserve">
<value>Calculated header checksum = 0x{0:X2}, {1}</value>

View File

@@ -0,0 +1,77 @@
// /***************************************************************************
// Aaru Data Preservation Suite
// ----------------------------------------------------------------------------
//
// Filename : Constants.cs
// Author(s) : Natalia Portillo <claunia@claunia.com>
//
// Component : Disk image plugins.
//
// --[ Description ] ----------------------------------------------------------
//
// Contains constants for Sydex CopyQM+ Self-eXtracting Disk (SXD) 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.Diagnostics.CodeAnalysis;
namespace Aaru.Images;
[SuppressMessage("ReSharper", "UnusedMember.Local")]
public sealed partial class SXD
{
/// <summary>SXD header signature bytes: 'S','X','D'.</summary>
static readonly byte[] _sxdSignature = [(byte)'S', (byte)'X', (byte)'D'];
/// <summary>Optional "WB" block signature bytes preceding the SXD payload.</summary>
static readonly byte[] _wbSignature = [(byte)'W', (byte)'B'];
/// <summary>Size of the SXD header, in bytes.</summary>
const int SXD_HEADER_SIZE = 33;
/// <summary>Number of header bytes protected by <c>crc_hdr</c>.</summary>
const int SXD_CRC_HDR_COVER = 31;
/// <summary>Reversed ANSI CRC-16 polynomial used by the SXD format.</summary>
const ushort SXD_CRC_POLY = 0xA001;
/// <summary>Maximum number of cylinders we will accept in a well-formed SXD image.</summary>
const int NTRK_MAX = 84;
/// <summary>All SXD tracks use 512-byte sectors.</summary>
const int SECTOR_SIZE = 512;
/// <summary>Fill byte used for tracks not present in the image (freshly formatted).</summary>
const byte FMT_BYTE = 0xF6;
/// <summary>SXD drive type codes as reported by the <c>drv_typ</c> header field.</summary>
enum SxdDriveType : byte
{
Unused = 0,
Floppy360K = 1,
Floppy12M = 2,
Floppy720K = 3,
Floppy144M = 4,
EightInch = 5,
Floppy288M = 6
}
}

172
Aaru.Images/SXD/Identify.cs Normal file
View File

@@ -0,0 +1,172 @@
// /***************************************************************************
// Aaru Data Preservation Suite
// ----------------------------------------------------------------------------
//
// Filename : Identify.cs
// Author(s) : Natalia Portillo <claunia@claunia.com>
//
// Component : Disk image plugins.
//
// --[ Description ] ----------------------------------------------------------
//
// Identifies Sydex CopyQM+ Self-eXtracting Disk (SXD) 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 Aaru.CommonTypes.Interfaces;
using Aaru.Helpers;
namespace Aaru.Images;
public sealed partial class SXD
{
#region IMediaImage Members
/// <inheritdoc />
public bool Identify(IFilter imageFilter)
{
Stream stream = imageFilter.GetDataForkStream();
return FindHeaderOffset(stream) >= 0;
}
#endregion
/// <summary>
/// Locate the SXD header within <paramref name="stream" />. SXD images are either "bare" (the 'SXD' signature at
/// offset 0) or wrapped in a DOS or DOS/OS-2 self-extracting executable stub. An optional "WB" block may sit
/// between the stub and the SXD payload.
/// </summary>
/// <returns>Offset of the SXD header in <paramref name="stream" />, or -1 if not found.</returns>
static long FindHeaderOffset(Stream stream)
{
if(stream.Length < SXD_HEADER_SIZE) return -1;
stream.Seek(0, SeekOrigin.Begin);
var first3 = new byte[3];
if(stream.EnsureRead(first3, 0, 3) != 3) return -1;
// Bare SXD image.
if(SignatureMatches(first3, _sxdSignature)) return 0;
// Otherwise must start with the 'MZ' DOS executable signature.
if(first3[0] != 'M' || first3[1] != 'Z') return -1;
long fileSize = stream.Length;
long hdrOfs = CalcSkip(stream, fileSize, 0);
if(hdrOfs < 0) return -1;
if(hdrOfs + SXD_HEADER_SIZE > fileSize) return -1;
stream.Seek(hdrOfs, SeekOrigin.Begin);
if(stream.EnsureRead(first3, 0, 3) != 3) return -1;
if(SignatureMatches(first3, _sxdSignature)) return hdrOfs;
// A 'WB' block may precede the actual SXD payload.
if(first3[0] != 'W' || first3[1] != 'B') return -1;
stream.Seek(hdrOfs, SeekOrigin.Begin);
hdrOfs = CalcSkip(stream, fileSize, hdrOfs);
if(hdrOfs < 0) return -1;
if(hdrOfs + SXD_HEADER_SIZE > fileSize) return -1;
stream.Seek(hdrOfs, SeekOrigin.Begin);
if(stream.EnsureRead(first3, 0, 3) != 3) return -1;
return SignatureMatches(first3, _sxdSignature) ? hdrOfs : -1;
}
/// <summary>
/// Skip a DOS-style stub/block by reading its MZ-header length (bytes 2..3 = last-block bytes, bytes 4..5 =
/// number of 512-byte blocks) from the current position, and computing the absolute offset of what follows.
/// </summary>
static long CalcSkip(Stream stream, long fileSize, long startOfs)
{
// The caller already consumed the first two bytes of the stub/block signature; skip to offset 2 of it.
stream.Seek(startOfs + 2, SeekOrigin.Begin);
var word = new byte[2];
if(stream.EnsureRead(word, 0, 2) != 2) return -1;
var lastBlkSz = (ushort)(word[0] | word[1] << 8);
if(lastBlkSz > 512) return -1;
if(stream.EnsureRead(word, 0, 2) != 2) return -1;
var nBlks = (ushort)(word[0] | word[1] << 8);
if(nBlks > 256) return -1;
long skipSz = nBlks * 512L - (512 - lastBlkSz);
if(skipSz <= 0 || skipSz > fileSize) return -1;
return startOfs + skipSz;
}
static bool SignatureMatches(ReadOnlySpan<byte> data, byte[] expected)
{
if(data.Length < expected.Length) return false;
for(var i = 0; i < expected.Length; i++)
{
if(data[i] != expected[i]) return false;
}
return true;
}
/// <summary>Compute the SXD CRC-16 (reversed poly 0xA001, initial value 0, non-inverted) over <paramref name="data" />.</summary>
static ushort SxdCrc16(ReadOnlySpan<byte> data)
{
ushort crc = 0;
for(var i = 0; i < data.Length; i++)
{
crc ^= data[i];
for(var b = 0; b < 8; b++)
{
if((crc & 1) != 0)
crc = (ushort)(crc >> 1 ^ SXD_CRC_POLY);
else
crc = (ushort)(crc >> 1);
}
}
return crc;
}
}

356
Aaru.Images/SXD/Lzh.cs Normal file
View File

@@ -0,0 +1,356 @@
// /***************************************************************************
// Aaru Data Preservation Suite
// ----------------------------------------------------------------------------
//
// Filename : Lzh.cs
// Author(s) : Natalia Portillo <claunia@claunia.com>
//
// Component : Disk image plugins.
//
// --[ Description ] ----------------------------------------------------------
//
// LZHUF / LH1-variant decompressor used by Sydex CopyQM+ Self-eXtracting
// Disk (SXD) images. Plugin-local, buffer based, single pass.
//
// Based on the work of Michal Necasek (fdimg), which is itself adapted
// from LZHUF by Haruyasu Yoshizaki (1988), as published in the English
// port edited by Kenji Rikitake. The only difference from the standard
// LH1 algorithm is that the sliding text buffer is pre-filled with zero
// bytes instead of spaces.
//
// --[ 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 SXD
{
/// <summary>SXD LH1/LZHUF decompressor. Buffer based, single-shot; reused per image.</summary>
sealed class Lzh
{
// LZSS parameters.
const int N = 4096;
const int F = 60;
const int THRESHOLD = 2;
// Huffman coding parameters.
const int N_CHAR = 256 - THRESHOLD + F;
const int T = N_CHAR * 2 - 1;
const int R = T - 1;
const int MAX_FREQ = 0x8000;
// Tables for decoding upper 6 bits of sliding dictionary pointer.
static readonly byte[] _dCode =
[
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x02, 0x02,
0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x03, 0x03, 0x03,
0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x04, 0x04, 0x04, 0x04, 0x04,
0x04, 0x04, 0x04, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
0x06, 0x06, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
0x08, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A,
0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0C, 0x0C, 0x0C, 0x0C, 0x0D, 0x0D, 0x0D, 0x0D, 0x0E,
0x0E, 0x0E, 0x0E, 0x0F, 0x0F, 0x0F, 0x0F, 0x10, 0x10, 0x10, 0x10, 0x11, 0x11, 0x11, 0x11, 0x12, 0x12,
0x12, 0x12, 0x13, 0x13, 0x13, 0x13, 0x14, 0x14, 0x14, 0x14, 0x15, 0x15, 0x15, 0x15, 0x16, 0x16, 0x16,
0x16, 0x17, 0x17, 0x17, 0x17, 0x18, 0x18, 0x19, 0x19, 0x1A, 0x1A, 0x1B, 0x1B, 0x1C, 0x1C, 0x1D, 0x1D,
0x1E, 0x1E, 0x1F, 0x1F, 0x20, 0x20, 0x21, 0x21, 0x22, 0x22, 0x23, 0x23, 0x24, 0x24, 0x25, 0x25, 0x26,
0x26, 0x27, 0x27, 0x28, 0x28, 0x29, 0x29, 0x2A, 0x2A, 0x2B, 0x2B, 0x2C, 0x2C, 0x2D, 0x2D, 0x2E, 0x2E,
0x2F, 0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E,
0x3F
];
static readonly byte[] _dLen =
[
0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x04, 0x04,
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x05, 0x05, 0x05, 0x05, 0x05,
0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
0x06, 0x06, 0x06, 0x06, 0x06, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
0x07, 0x07, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
0x08
];
readonly ushort[] _freq = new ushort[T + 1];
readonly int[] _prnt = new int[T + N_CHAR];
readonly short[] _son = new short[T];
readonly byte[] _textBuf = new byte[N + F - 1];
ushort _getbuf;
byte _getlen;
byte[] _in;
int _inEnd;
int _inPos;
byte[] _out;
int _outEnd;
int _outPos;
/// <summary>
/// Decompress a single SXD LH1 block from <paramref name="src" /> into <paramref name="dst" /> at
/// <paramref name="dstOfs" />.
/// </summary>
/// <param name="src">Compressed track data.</param>
/// <param name="srcLength">Number of valid bytes in <paramref name="src" />.</param>
/// <param name="dst">Destination buffer.</param>
/// <param name="dstOfs">Offset in <paramref name="dst" /> at which to write the uncompressed data.</param>
/// <param name="dstLength">Expected uncompressed size.</param>
/// <returns>Number of bytes written. Should equal <paramref name="dstLength" /> on success.</returns>
public int Decode(byte[] src, int srcLength, byte[] dst, int dstOfs, int dstLength)
{
_in = src;
_inPos = 0;
_inEnd = srcLength;
_out = dst;
_outPos = dstOfs;
_outEnd = dstOfs + dstLength;
_getbuf = 0;
_getlen = 0;
StartHuff();
// Pre-initialize text buffer with zero bytes (the key difference from LH1 used in LHarc).
for(var i = 0; i < N - F; i++) _textBuf[i] = 0;
int r = N - F;
var count = 0;
while(count < dstLength)
{
int c = DecodeChar();
if(c < 256)
{
WriteByte((byte)c);
_textBuf[r++] = (byte)c;
r &= N - 1;
count++;
}
else
{
int i = r - DecodePosition() - 1 & N - 1;
int j = c - 255 + THRESHOLD;
for(var k = 0; k < j; k++)
{
byte b = _textBuf[i + k & N - 1];
WriteByte(b);
_textBuf[r++] = b;
r &= N - 1;
count++;
if(count >= dstLength) break;
}
}
}
return count;
}
int ReadByte()
{
if(_inPos < _inEnd) return _in[_inPos++];
return -1;
}
void WriteByte(byte c)
{
if(_outPos < _outEnd) _out[_outPos++] = c;
}
int GetBit()
{
while(_getlen <= 8)
{
int b = ReadByte();
if(b < 0) b = 0;
_getbuf |= (ushort)(b << 8 - _getlen);
_getlen += 8;
}
bool msb = (_getbuf & 0x8000) != 0;
_getbuf <<= 1;
_getlen--;
return msb ? 1 : 0;
}
int GetByte()
{
while(_getlen <= 8)
{
int b = ReadByte();
if(b < 0) b = 0;
_getbuf |= (ushort)(b << 8 - _getlen);
_getlen += 8;
}
int i = _getbuf;
_getbuf <<= 8;
_getlen -= 8;
return (ushort)i >> 8;
}
void StartHuff()
{
int i;
for(i = 0; i < N_CHAR; i++)
{
_freq[i] = 1;
_son[i] = (short)(i + T);
_prnt[i + T] = i;
}
i = 0;
int j = N_CHAR;
while(j <= R)
{
_freq[j] = (ushort)(_freq[i] + _freq[i + 1]);
_son[j] = (short)i;
_prnt[i] = _prnt[i + 1] = j;
i += 2;
j++;
}
_freq[T] = 0xFFFF;
_prnt[R] = 0;
}
void Reconst()
{
int i, j, k;
j = 0;
for(i = 0; i < T; i++)
{
if(_son[i] >= T)
{
_freq[j] = (ushort)((_freq[i] + 1) / 2);
_son[j] = _son[i];
j++;
}
}
for(i = 0, j = N_CHAR; j < T; i += 2, j++)
{
k = i + 1;
ushort f = _freq[j] = (ushort)(_freq[i] + _freq[k]);
for(k = j - 1; f < _freq[k]; k--) {}
k++;
// Shift freq[k..j-1] right by one.
for(int m = j; m > k; m--) _freq[m] = _freq[m - 1];
_freq[k] = f;
for(int m = j; m > k; m--) _son[m] = _son[m - 1];
_son[k] = (short)i;
}
for(i = 0; i < T; i++)
{
k = _son[i];
if(k >= T)
_prnt[k] = i;
else
_prnt[k] = _prnt[k + 1] = i;
}
}
void Update(int c)
{
if(_freq[R] == MAX_FREQ) Reconst();
c = _prnt[c + T];
do
{
int k = ++_freq[c];
int l = c + 1;
if(k > _freq[l])
{
while(k > _freq[++l]) {}
l--;
_freq[c] = _freq[l];
_freq[l] = (ushort)k;
int i = _son[c];
_prnt[i] = l;
if(i < T) _prnt[i + 1] = l;
int jj = _son[l];
_son[l] = (short)i;
_prnt[jj] = c;
if(jj < T) _prnt[jj + 1] = c;
_son[c] = (short)jj;
c = l;
}
c = _prnt[c];
} while(c != 0);
}
int DecodeChar()
{
int c = _son[R];
while(c < T)
{
c += GetBit();
c = _son[c];
}
c -= T;
Update(c);
return c;
}
int DecodePosition()
{
int i = GetByte();
int c = _dCode[i] << 6;
int j = _dLen[i];
j -= 2;
while(j-- != 0) i = (i << 1) + GetBit();
return c | i & 0x3F;
}
}
}

339
Aaru.Images/SXD/Open.cs Normal file
View File

@@ -0,0 +1,339 @@
// /***************************************************************************
// Aaru Data Preservation Suite
// ----------------------------------------------------------------------------
//
// Filename : Open.cs
// Author(s) : Natalia Portillo <claunia@claunia.com>
//
// Component : Disk image plugins.
//
// --[ Description ] ----------------------------------------------------------
//
// Opens Sydex CopyQM+ Self-eXtracting Disk (SXD) 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.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 SXD
{
#region IMediaImage Members
/// <inheritdoc />
public ErrorNumber Open(IFilter imageFilter)
{
Stream stream = imageFilter.GetDataForkStream();
long hdrOfs = FindHeaderOffset(stream);
if(hdrOfs < 0) return ErrorNumber.InvalidArgument;
_headerOffset = hdrOfs;
var rawHeader = new byte[SXD_HEADER_SIZE];
stream.Seek(hdrOfs, SeekOrigin.Begin);
if(stream.EnsureRead(rawHeader, 0, SXD_HEADER_SIZE) != SXD_HEADER_SIZE) return ErrorNumber.InvalidArgument;
_header = Marshal.ByteArrayToStructureLittleEndian<Header>(rawHeader);
ushort computedHdrCrc = SxdCrc16(rawHeader.AsSpan(0, SXD_CRC_HDR_COVER));
AaruLogging.Debug(MODULE_NAME, "header.trk_len = {0}", _header.trk_len);
AaruLogging.Debug(MODULE_NAME, "header.n_trk_sec = {0}", _header.n_trk_sec);
AaruLogging.Debug(MODULE_NAME, "header.n_heads = {0}", _header.n_heads);
AaruLogging.Debug(MODULE_NAME, "header.n_cyl_dsk = {0}", _header.n_cyl_dsk);
AaruLogging.Debug(MODULE_NAME, "header.n_cyl_img = {0}", _header.n_cyl_img);
AaruLogging.Debug(MODULE_NAME, "header.first_sid = {0}", _header.first_sid);
AaruLogging.Debug(MODULE_NAME, "header.interleave = {0}", _header.interleave);
AaruLogging.Debug(MODULE_NAME, "header.skew = {0}", _header.skew);
AaruLogging.Debug(MODULE_NAME, "header.sec_sz_code = {0}", _header.sec_sz_code);
AaruLogging.Debug(MODULE_NAME, "header.drv_typ = {0} ({1})", _header.drv_typ, (SxdDriveType)_header.drv_typ);
AaruLogging.Debug(MODULE_NAME, "header.density = {0} ({1})", _header.density, DensityName(_header.density));
AaruLogging.Debug(MODULE_NAME, "header.pwd_crc = 0x{0:X4}", _header.pwd_crc);
AaruLogging.Debug(MODULE_NAME, "header.pwd_hash = 0x{0:X4}", _header.pwd_hash);
AaruLogging.Debug(MODULE_NAME, "header.comment_len = {0}", _header.comment_len);
AaruLogging.Debug(MODULE_NAME, "header.crc_comment = 0x{0:X4}", _header.crc_comment);
AaruLogging.Debug(MODULE_NAME,
"header.unk0 = {0:X2} {1:X2} {2:X2} {3:X2}",
_header.unk0[0],
_header.unk0[1],
_header.unk0[2],
_header.unk0[3]);
AaruLogging.Debug(MODULE_NAME, "header.dos_time = 0x{0:X4}", _header.dos_time);
AaruLogging.Debug(MODULE_NAME, "header.dos_date = 0x{0:X4}", _header.dos_date);
AaruLogging.Debug(MODULE_NAME,
"header.crc_hdr = 0x{0:X4} (computed 0x{1:X4})",
_header.crc_hdr,
computedHdrCrc);
if(computedHdrCrc != _header.crc_hdr)
{
AaruLogging.Error(Localization.Sxd_header_CRC_mismatch);
return ErrorNumber.InvalidArgument;
}
// Password-protected images cannot be decoded; the scheme is not documented.
if(_header.pwd_crc != 0 || _header.pwd_hash != 0)
{
AaruLogging.Error(Localization.Sxd_image_is_password_protected_and_unsupported);
return ErrorNumber.NotSupported;
}
byte spt = _header.n_trk_sec;
byte heads = _header.n_heads;
byte cylsDsk = _header.n_cyl_dsk;
byte cylsImg = _header.n_cyl_img;
// Basic sanity checks matching fdimg's sxd_open().
if(spt == 0 || heads is 0 or > 2 || cylsDsk == 0 || cylsImg == 0 || cylsDsk > NTRK_MAX || cylsImg > cylsDsk)
{
AaruLogging.Error(Localization.Sxd_image_has_invalid_geometry);
return ErrorNumber.InvalidArgument;
}
if(_header.trk_len != spt * SECTOR_SIZE)
{
AaruLogging.Error(Localization.Sxd_inconsistent_track_length);
return ErrorNumber.InvalidArgument;
}
int trackLen = spt * SECTOR_SIZE;
// Read optional comment following the header.
long dataOffset = _headerOffset + SXD_HEADER_SIZE + _header.comment_len;
if(_header.comment_len > 0)
{
var commentBytes = new byte[_header.comment_len];
stream.Seek(_headerOffset + SXD_HEADER_SIZE, SeekOrigin.Begin);
if(stream.EnsureRead(commentBytes, 0, _header.comment_len) != _header.comment_len)
return ErrorNumber.InvalidArgument;
ushort commentCrc = SxdCrc16(commentBytes);
if(commentCrc == _header.crc_comment)
{
// Replace embedded nulls with newlines, mirroring fdimg.
for(var i = 0; i < commentBytes.Length; i++)
{
if(commentBytes[i] == 0) commentBytes[i] = (byte)'\n';
}
_imageInfo.Comments = Encoding.GetEncoding("ibm437").GetString(commentBytes);
}
else
AaruLogging.Debug(MODULE_NAME, Localization.Sxd_comment_CRC_mismatch_discarded);
}
// Allocate flat disk buffer sized to the full physical disk and pre-fill with the freshly-formatted byte.
long totalSectors = (long)cylsDsk * heads * spt;
_decodedDisk = new byte[totalSectors * SECTOR_SIZE];
Array.Fill(_decodedDisk, FMT_BYTE);
// Decompress every track stored in the image into its slot in the flat buffer.
int tracksInImage = cylsImg * heads;
Lzh lzh = null;
var cmprBuf = new byte[trackLen + 128];
var allCrcsOk = true;
stream.Seek(dataOffset, SeekOrigin.Begin);
for(var trkIdx = 0; trkIdx < tracksInImage; trkIdx++)
{
long dstOfs = (long)trkIdx * trackLen;
ErrorNumber err = DecompressTrack(stream,
_decodedDisk,
(int)dstOfs,
trackLen,
cmprBuf,
ref lzh,
out bool trackCrcOk);
if(err != ErrorNumber.NoError)
{
AaruLogging.Error(Localization.Sxd_track_decompression_failed_0, trkIdx);
return err;
}
if(!trackCrcOk) allCrcsOk = false;
}
_dataCrcOk = allCrcsOk;
// Populate ImageInfo.
_imageInfo.Application = "CopyQM+ / MAKESXD";
_imageInfo.CreationTime = DosDateTimeToUtc(_header.dos_date, _header.dos_time, imageFilter.CreationTime);
_imageInfo.LastModificationTime = _imageInfo.CreationTime;
_imageInfo.MediaTitle = imageFilter.Filename;
_imageInfo.ImageSize = (ulong)(stream.Length - dataOffset);
_imageInfo.Sectors = (ulong)totalSectors;
_imageInfo.SectorSize = SECTOR_SIZE;
_imageInfo.Cylinders = cylsDsk;
_imageInfo.Heads = heads;
_imageInfo.SectorsPerTrack = spt;
_imageInfo.MetadataMediaType = MetadataMediaType.BlockMedia;
_imageInfo.MediaType = Geometry.GetMediaType((cylsDsk, heads, spt, SECTOR_SIZE, MediaEncoding.MFM, false));
AaruLogging.Verbose(Localization.Sxd_image_contains_a_disk_of_type_0, _imageInfo.MediaType);
if(!string.IsNullOrEmpty(_imageInfo.Comments))
AaruLogging.Verbose(Localization.Sxd_comments_0, _imageInfo.Comments);
AaruLogging.Debug(MODULE_NAME, Localization.Sxd_all_track_CRCs_match_0, _dataCrcOk);
return ErrorNumber.NoError;
}
#endregion
/// <summary>Read, decompress, and CRC-validate one track from <paramref name="stream" /> into <paramref name="outBuf" />.</summary>
ErrorNumber DecompressTrack(Stream stream, byte[] outBuf, int outOfs, int trackLen, byte[] cmprBuf, ref Lzh lzh,
out bool crcOk)
{
crcOk = false;
var header = new byte[4];
if(stream.EnsureRead(header, 0, 4) != 4) return ErrorNumber.InvalidArgument;
var crcStored = (ushort)(header[0] | header[1] << 8);
var cmprLen = (short)(header[2] | header[3] << 8);
int cmprLenAbs = cmprLen < 0 ? -cmprLen : cmprLen;
if(cmprLenAbs > trackLen + 2)
{
AaruLogging.Error(Localization.Sxd_track_compressed_length_out_of_range_0, cmprLen);
return ErrorNumber.InvalidArgument;
}
if(stream.EnsureRead(cmprBuf, 0, cmprLenAbs) != cmprLenAbs) return ErrorNumber.InvalidArgument;
if(cmprLen < 0)
{
// CopyQM-style signed-RLE (inline).
var cmprPos = 0;
var bytesWritten = 0;
while(bytesWritten < trackLen)
{
if(cmprPos + 2 > cmprLenAbs) return ErrorNumber.InvalidArgument;
var repCnt = (short)(cmprBuf[cmprPos] | cmprBuf[cmprPos + 1] << 8);
cmprPos += 2;
if(repCnt > 0)
{
if(bytesWritten + repCnt > trackLen) return ErrorNumber.InvalidArgument;
if(cmprPos + repCnt > cmprLenAbs) return ErrorNumber.InvalidArgument;
Buffer.BlockCopy(cmprBuf, cmprPos, outBuf, outOfs + bytesWritten, repCnt);
cmprPos += repCnt;
bytesWritten += repCnt;
}
else if(repCnt < 0)
{
int count = -repCnt;
if(bytesWritten + count > trackLen) return ErrorNumber.InvalidArgument;
if(cmprPos + 1 > cmprLenAbs) return ErrorNumber.InvalidArgument;
byte b = cmprBuf[cmprPos++];
for(var i = 0; i < count; i++) outBuf[outOfs + bytesWritten + i] = b;
bytesWritten += count;
}
else
{
// Zero run-count is invalid per fdimg.
return ErrorNumber.InvalidArgument;
}
}
}
else
{
// LH1-variant LZHUF.
lzh ??= new Lzh();
int produced = lzh.Decode(cmprBuf, cmprLenAbs, outBuf, outOfs, trackLen);
if(produced != trackLen) return ErrorNumber.InvalidArgument;
}
ushort crcComputed = SxdCrc16(outBuf.AsSpan(outOfs, trackLen));
crcOk = crcComputed == crcStored;
return ErrorNumber.NoError;
}
static string DensityName(byte density) => density switch
{
0 => "DD",
1 => "HD",
2 => "ED",
_ => "unknown"
};
static DateTime DosDateTimeToUtc(ushort dosDate, ushort dosTime, DateTime fallback)
{
if(dosDate == 0 && dosTime == 0) return fallback;
int year = (dosDate >> 9 & 0x7F) + 1980;
int month = dosDate >> 5 & 0x0F;
int day = dosDate & 0x1F;
int hour = dosTime >> 11 & 0x1F;
int minute = dosTime >> 5 & 0x3F;
int second = (dosTime & 0x1F) * 2;
try
{
return new DateTime(year, month, day, hour, minute, second, DateTimeKind.Unspecified);
}
catch(ArgumentOutOfRangeException)
{
return fallback;
}
}
}

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 Sydex CopyQM+ Self-eXtracting Disk (SXD) 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 SXD
{
#region IMediaImage Members
/// <inheritdoc />
// ReSharper disable once ConvertToAutoProperty
public ImageInfo Info => _imageInfo;
/// <inheritdoc />
public string Name => Localization.Sxd_Name;
/// <inheritdoc />
public Guid Id => new("B3C6E8F2-4A2D-4F10-9E1C-8D3A7B5F2E94");
/// <inheritdoc />
public string Format => "SXD";
/// <inheritdoc />
public string Author => Authors.NataliaPortillo;
/// <inheritdoc />
public List<DumpHardware> DumpHardware => null;
/// <inheritdoc />
public Metadata AaruMetadata => null;
#endregion
}

79
Aaru.Images/SXD/Read.cs Normal file
View File

@@ -0,0 +1,79 @@
// /***************************************************************************
// Aaru Data Preservation Suite
// ----------------------------------------------------------------------------
//
// Filename : Read.cs
// Author(s) : Natalia Portillo <claunia@claunia.com>
//
// Component : Disk image plugins.
//
// --[ Description ] ----------------------------------------------------------
//
// Reads Sydex CopyQM+ Self-eXtracting Disk (SXD) 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.Linq;
using Aaru.CommonTypes.Enums;
namespace Aaru.Images;
public sealed partial class SXD
{
#region IMediaImage Members
/// <inheritdoc />
public ErrorNumber ReadSector(ulong sectorAddress, bool negative, out byte[] buffer, out SectorStatus sectorStatus)
{
sectorStatus = SectorStatus.Dumped;
return ReadSectors(sectorAddress, negative, 1, out buffer, out _);
}
/// <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 - 1) return ErrorNumber.OutOfRange;
if(sectorAddress + length > _imageInfo.Sectors) return ErrorNumber.OutOfRange;
buffer = new byte[length * _imageInfo.SectorSize];
sectorStatus = Enumerable.Repeat(SectorStatus.Dumped, (int)length).ToArray();
Array.Copy(_decodedDisk,
(long)sectorAddress * _imageInfo.SectorSize,
buffer,
0,
length * _imageInfo.SectorSize);
return ErrorNumber.NoError;
}
#endregion
}

74
Aaru.Images/SXD/SXD.cs Normal file
View File

@@ -0,0 +1,74 @@
// /***************************************************************************
// Aaru Data Preservation Suite
// ----------------------------------------------------------------------------
//
// Filename : SXD.cs
// Author(s) : Natalia Portillo <claunia@claunia.com>
//
// Component : Disk image plugins.
//
// --[ Description ] ----------------------------------------------------------
//
// Manages Sydex CopyQM+ Self-eXtracting Disk (SXD) 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.Interfaces;
using Aaru.CommonTypes.Structs;
namespace Aaru.Images;
/// <inheritdoc cref="Aaru.CommonTypes.Interfaces.IMediaImage" />
/// <summary>Implements reading Sydex CopyQM+ Self-eXtracting Disk (SXD) images.</summary>
public sealed partial class SXD : IMediaImage, IVerifiableImage
{
const string MODULE_NAME = "SXD plugin";
bool _dataCrcOk;
byte[] _decodedDisk;
Header _header;
long _headerOffset;
ImageInfo _imageInfo;
public SXD() => _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,92 @@
// /***************************************************************************
// Aaru Data Preservation Suite
// ----------------------------------------------------------------------------
//
// Filename : Structs.cs
// Author(s) : Natalia Portillo <claunia@claunia.com>
//
// Component : Disk image plugins.
//
// --[ Description ] ----------------------------------------------------------
//
// Contains structures for Sydex CopyQM+ Self-eXtracting Disk (SXD) 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 SXD
{
#region Nested type: Header
/// <summary>SXD image header, exactly 33 bytes long. The first 31 bytes are protected by <see cref="crc_hdr" />.</summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
readonly struct Header
{
/// <summary>0x00 'SXD' signature.</summary>
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
public readonly byte[] signature;
/// <summary>0x03 Track length in bytes.</summary>
public readonly ushort trk_len;
/// <summary>0x05 Number of sectors per track.</summary>
public readonly byte n_trk_sec;
/// <summary>0x06 Number of heads (1 or 2 valid).</summary>
public readonly byte n_heads;
/// <summary>0x07 Number of cylinders on disk.</summary>
public readonly byte n_cyl_dsk;
/// <summary>0x08 Number of cylinders stored in the image.</summary>
public readonly byte n_cyl_img;
/// <summary>0x09 ID of first sector minus 1 (usually 0).</summary>
public readonly byte first_sid;
/// <summary>0x0A Interleave factor (usually 1).</summary>
public readonly byte interleave;
/// <summary>0x0B Skew factor, probably (usually 0).</summary>
public readonly byte skew;
/// <summary>0x0C Sector size code (2 = 512B).</summary>
public readonly byte sec_sz_code;
/// <summary>0x0D Drive type, 1-6 (see <see cref="SxdDriveType" />).</summary>
public readonly byte drv_typ;
/// <summary>0x0E Density: 0/1/2 for DD/HD/ED.</summary>
public readonly byte density;
/// <summary>0x0F Password CRC (nonzero if image is encrypted).</summary>
public readonly ushort pwd_crc;
/// <summary>0x11 Password hash.</summary>
public readonly ushort pwd_hash;
/// <summary>0x13 Length of optional comment following the header.</summary>
public readonly ushort comment_len;
/// <summary>0x15 CRC of the comment block.</summary>
public readonly ushort crc_comment;
/// <summary>0x17 Unknown 4 bytes.</summary>
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
public readonly byte[] unk0;
/// <summary>0x1B Image timestamp in DOS time format.</summary>
public readonly ushort dos_time;
/// <summary>0x1D Image date in DOS date format.</summary>
public readonly ushort dos_date;
/// <summary>0x1F CRC of header bytes 0x00..0x1E.</summary>
public readonly ushort crc_hdr;
}
#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 Sydex CopyQM+ Self-eXtracting Disk (SXD) 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 SXD
{
#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
}

45
Aaru.Images/SXD/Verify.cs Normal file
View File

@@ -0,0 +1,45 @@
// /***************************************************************************
// Aaru Data Preservation Suite
// ----------------------------------------------------------------------------
//
// Filename : Verify.cs
// Author(s) : Natalia Portillo <claunia@claunia.com>
//
// Component : Disk image plugins.
//
// --[ Description ] ----------------------------------------------------------
//
// Verifies Sydex CopyQM+ Self-eXtracting Disk (SXD) 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 SXD
{
#region IVerifiableImage Members
/// <inheritdoc />
public bool? VerifyMediaImage() => _dataCrcOk;
#endregion
}

View File

@@ -114,6 +114,7 @@ Media image formats we can read
* Software Pirates SNATCH-IT / Central Point COPY II-PC (.CP2)
* Spectrum floppy disk image (.FDI)
* SuperCardPro
* Sydex CopyQM+ Self-eXtracting Disk (.SXD)
* TeleDisk
* UltraISO (.ISZ)
* WinOnCD (.C2D)