diff --git a/Aaru.Images/Localization/Localization.Designer.cs b/Aaru.Images/Localization/Localization.Designer.cs index a2eaf0764..e0d7152cf 100644 --- a/Aaru.Images/Localization/Localization.Designer.cs +++ b/Aaru.Images/Localization/Localization.Designer.cs @@ -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); diff --git a/Aaru.Images/Localization/Localization.es.resx b/Aaru.Images/Localization/Localization.es.resx index fda6d75ab..d7684a434 100644 --- a/Aaru.Images/Localization/Localization.es.resx +++ b/Aaru.Images/Localization/Localization.es.resx @@ -487,6 +487,39 @@ Comentarios DXP: {0} + + + Disco Auto-extraíble Sydex CopyQM+ + + + El CRC de la cabecera SXD no coincide; la imagen está dañada o no es SXD. + + + La imagen SXD está protegida por contraseña; el descifrado no está soportado. + + + La imagen SXD indica una geometría de disco inválida. + + + La longitud de pista de la cabecera SXD es inconsistente con los sectores por pista. + + + El CRC del comentario SXD no coincide; comentario descartado. + + + Falló la descompresión de la pista SXD {0}. + + + La pista SXD tiene una longitud comprimida fuera de rango {0}. + + + La imagen SXD contiene un disco de tipo {0} + + + Comentarios SXD: {0} + + + CRCs de todas las pistas SXD validados = {0} Texto de copyright: {0} diff --git a/Aaru.Images/Localization/Localization.resx b/Aaru.Images/Localization/Localization.resx index 64329ec3d..e349c29ad 100644 --- a/Aaru.Images/Localization/Localization.resx +++ b/Aaru.Images/Localization/Localization.resx @@ -2003,6 +2003,39 @@ DXP comments: {0} + + + Sydex CopyQM+ Self-eXtracting Disk + + + SXD header CRC does not match; image is corrupt or not an SXD image. + + + SXD image is password protected; decryption is not supported. + + + SXD image reports invalid disk geometry. + + + SXD header track length is inconsistent with sectors per track. + + + SXD comment CRC mismatch; comment discarded. + + + Decompression failed for SXD track {0}. + + + SXD track has out-of-range compressed length {0}. + + + SXD image contains a disk of type {0} + + + SXD comments: {0} + + + All SXD track CRCs validated = {0} Calculated header checksum = 0x{0:X2}, {1} diff --git a/Aaru.Images/SXD/Constants.cs b/Aaru.Images/SXD/Constants.cs new file mode 100644 index 000000000..b855f2083 --- /dev/null +++ b/Aaru.Images/SXD/Constants.cs @@ -0,0 +1,77 @@ +// /*************************************************************************** +// Aaru Data Preservation Suite +// ---------------------------------------------------------------------------- +// +// Filename : Constants.cs +// Author(s) : Natalia Portillo +// +// 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 . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2026 Natalia Portillo +// ****************************************************************************/ + +using System.Diagnostics.CodeAnalysis; + +namespace Aaru.Images; + +[SuppressMessage("ReSharper", "UnusedMember.Local")] +public sealed partial class SXD +{ + /// SXD header signature bytes: 'S','X','D'. + static readonly byte[] _sxdSignature = [(byte)'S', (byte)'X', (byte)'D']; + + /// Optional "WB" block signature bytes preceding the SXD payload. + static readonly byte[] _wbSignature = [(byte)'W', (byte)'B']; + + /// Size of the SXD header, in bytes. + const int SXD_HEADER_SIZE = 33; + + /// Number of header bytes protected by crc_hdr. + const int SXD_CRC_HDR_COVER = 31; + + /// Reversed ANSI CRC-16 polynomial used by the SXD format. + const ushort SXD_CRC_POLY = 0xA001; + + /// Maximum number of cylinders we will accept in a well-formed SXD image. + const int NTRK_MAX = 84; + + /// All SXD tracks use 512-byte sectors. + const int SECTOR_SIZE = 512; + + /// Fill byte used for tracks not present in the image (freshly formatted). + const byte FMT_BYTE = 0xF6; + + /// SXD drive type codes as reported by the drv_typ header field. + enum SxdDriveType : byte + { + Unused = 0, + Floppy360K = 1, + Floppy12M = 2, + Floppy720K = 3, + Floppy144M = 4, + EightInch = 5, + Floppy288M = 6 + } +} \ No newline at end of file diff --git a/Aaru.Images/SXD/Identify.cs b/Aaru.Images/SXD/Identify.cs new file mode 100644 index 000000000..c0b3770c1 --- /dev/null +++ b/Aaru.Images/SXD/Identify.cs @@ -0,0 +1,172 @@ +// /*************************************************************************** +// Aaru Data Preservation Suite +// ---------------------------------------------------------------------------- +// +// Filename : Identify.cs +// Author(s) : Natalia Portillo +// +// 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 . +// +// ---------------------------------------------------------------------------- +// 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 + + /// + public bool Identify(IFilter imageFilter) + { + Stream stream = imageFilter.GetDataForkStream(); + + return FindHeaderOffset(stream) >= 0; + } + +#endregion + + /// + /// Locate the SXD header within . 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. + /// + /// Offset of the SXD header in , or -1 if not found. + 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; + } + + /// + /// 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. + /// + 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 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; + } + + /// Compute the SXD CRC-16 (reversed poly 0xA001, initial value 0, non-inverted) over . + static ushort SxdCrc16(ReadOnlySpan 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; + } +} \ No newline at end of file diff --git a/Aaru.Images/SXD/Lzh.cs b/Aaru.Images/SXD/Lzh.cs new file mode 100644 index 000000000..2f16993d6 --- /dev/null +++ b/Aaru.Images/SXD/Lzh.cs @@ -0,0 +1,356 @@ +// /*************************************************************************** +// Aaru Data Preservation Suite +// ---------------------------------------------------------------------------- +// +// Filename : Lzh.cs +// Author(s) : Natalia Portillo +// +// 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 . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2026 Natalia Portillo +// ****************************************************************************/ + +namespace Aaru.Images; + +public sealed partial class SXD +{ + /// SXD LH1/LZHUF decompressor. Buffer based, single-shot; reused per image. + 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; + + /// + /// Decompress a single SXD LH1 block from into at + /// . + /// + /// Compressed track data. + /// Number of valid bytes in . + /// Destination buffer. + /// Offset in at which to write the uncompressed data. + /// Expected uncompressed size. + /// Number of bytes written. Should equal on success. + 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; + } + } +} \ No newline at end of file diff --git a/Aaru.Images/SXD/Open.cs b/Aaru.Images/SXD/Open.cs new file mode 100644 index 000000000..ac91f3bd6 --- /dev/null +++ b/Aaru.Images/SXD/Open.cs @@ -0,0 +1,339 @@ +// /*************************************************************************** +// Aaru Data Preservation Suite +// ---------------------------------------------------------------------------- +// +// Filename : Open.cs +// Author(s) : Natalia Portillo +// +// 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 . +// +// ---------------------------------------------------------------------------- +// 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 + + /// + 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
(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 + + /// Read, decompress, and CRC-validate one track from into . + 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; + } + } +} \ No newline at end of file diff --git a/Aaru.Images/SXD/Properties.cs b/Aaru.Images/SXD/Properties.cs new file mode 100644 index 000000000..338cdef2f --- /dev/null +++ b/Aaru.Images/SXD/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 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 . +// +// ---------------------------------------------------------------------------- +// 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 + + /// + + // ReSharper disable once ConvertToAutoProperty + public ImageInfo Info => _imageInfo; + + /// + public string Name => Localization.Sxd_Name; + + /// + public Guid Id => new("B3C6E8F2-4A2D-4F10-9E1C-8D3A7B5F2E94"); + + /// + public string Format => "SXD"; + + /// + 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/SXD/Read.cs b/Aaru.Images/SXD/Read.cs new file mode 100644 index 000000000..9a0bd9433 --- /dev/null +++ b/Aaru.Images/SXD/Read.cs @@ -0,0 +1,79 @@ +// /*************************************************************************** +// Aaru Data Preservation Suite +// ---------------------------------------------------------------------------- +// +// Filename : Read.cs +// Author(s) : Natalia Portillo +// +// 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 . +// +// ---------------------------------------------------------------------------- +// 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 + + /// + public ErrorNumber ReadSector(ulong sectorAddress, bool negative, out byte[] buffer, out SectorStatus sectorStatus) + { + sectorStatus = SectorStatus.Dumped; + + return ReadSectors(sectorAddress, negative, 1, out buffer, out _); + } + + /// + 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 +} \ No newline at end of file diff --git a/Aaru.Images/SXD/SXD.cs b/Aaru.Images/SXD/SXD.cs new file mode 100644 index 000000000..e16d853a8 --- /dev/null +++ b/Aaru.Images/SXD/SXD.cs @@ -0,0 +1,74 @@ +// /*************************************************************************** +// Aaru Data Preservation Suite +// ---------------------------------------------------------------------------- +// +// Filename : SXD.cs +// Author(s) : Natalia Portillo +// +// 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 . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2026 Natalia Portillo +// ****************************************************************************/ + +using Aaru.CommonTypes.Interfaces; +using Aaru.CommonTypes.Structs; + +namespace Aaru.Images; + +/// +/// Implements reading Sydex CopyQM+ Self-eXtracting Disk (SXD) images. +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 + }; +} \ No newline at end of file diff --git a/Aaru.Images/SXD/Structs.cs b/Aaru.Images/SXD/Structs.cs new file mode 100644 index 000000000..db908baf9 --- /dev/null +++ b/Aaru.Images/SXD/Structs.cs @@ -0,0 +1,92 @@ +// /*************************************************************************** +// Aaru Data Preservation Suite +// ---------------------------------------------------------------------------- +// +// Filename : Structs.cs +// Author(s) : Natalia Portillo +// +// 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 . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2026 Natalia Portillo +// ****************************************************************************/ + +using System.Runtime.InteropServices; + +namespace Aaru.Images; + +public sealed partial class SXD +{ +#region Nested type: Header + + /// SXD image header, exactly 33 bytes long. The first 31 bytes are protected by . + [StructLayout(LayoutKind.Sequential, Pack = 1)] + readonly struct Header + { + /// 0x00 'SXD' signature. + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] + public readonly byte[] signature; + /// 0x03 Track length in bytes. + public readonly ushort trk_len; + /// 0x05 Number of sectors per track. + public readonly byte n_trk_sec; + /// 0x06 Number of heads (1 or 2 valid). + public readonly byte n_heads; + /// 0x07 Number of cylinders on disk. + public readonly byte n_cyl_dsk; + /// 0x08 Number of cylinders stored in the image. + public readonly byte n_cyl_img; + /// 0x09 ID of first sector minus 1 (usually 0). + public readonly byte first_sid; + /// 0x0A Interleave factor (usually 1). + public readonly byte interleave; + /// 0x0B Skew factor, probably (usually 0). + public readonly byte skew; + /// 0x0C Sector size code (2 = 512B). + public readonly byte sec_sz_code; + /// 0x0D Drive type, 1-6 (see ). + public readonly byte drv_typ; + /// 0x0E Density: 0/1/2 for DD/HD/ED. + public readonly byte density; + /// 0x0F Password CRC (nonzero if image is encrypted). + public readonly ushort pwd_crc; + /// 0x11 Password hash. + public readonly ushort pwd_hash; + /// 0x13 Length of optional comment following the header. + public readonly ushort comment_len; + /// 0x15 CRC of the comment block. + public readonly ushort crc_comment; + /// 0x17 Unknown 4 bytes. + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public readonly byte[] unk0; + /// 0x1B Image timestamp in DOS time format. + public readonly ushort dos_time; + /// 0x1D Image date in DOS date format. + public readonly ushort dos_date; + /// 0x1F CRC of header bytes 0x00..0x1E. + public readonly ushort crc_hdr; + } + +#endregion +} \ No newline at end of file diff --git a/Aaru.Images/SXD/Unsupported.cs b/Aaru.Images/SXD/Unsupported.cs new file mode 100644 index 000000000..2cdc7538b --- /dev/null +++ b/Aaru.Images/SXD/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 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 . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2026 Natalia Portillo +// ****************************************************************************/ + +using Aaru.CommonTypes.Enums; + +namespace Aaru.Images; + +public sealed partial class SXD +{ +#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/Aaru.Images/SXD/Verify.cs b/Aaru.Images/SXD/Verify.cs new file mode 100644 index 000000000..fd61c59dc --- /dev/null +++ b/Aaru.Images/SXD/Verify.cs @@ -0,0 +1,45 @@ +// /*************************************************************************** +// Aaru Data Preservation Suite +// ---------------------------------------------------------------------------- +// +// Filename : Verify.cs +// Author(s) : Natalia Portillo +// +// 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 . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2026 Natalia Portillo +// ****************************************************************************/ + +namespace Aaru.Images; + +public sealed partial class SXD +{ +#region IVerifiableImage Members + + /// + public bool? VerifyMediaImage() => _dataCrcOk; + +#endregion +} \ No newline at end of file diff --git a/README.md b/README.md index 6da00e9a5..5918f410a 100644 --- a/README.md +++ b/README.md @@ -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)