mirror of
https://github.com/aaru-dps/Aaru.git
synced 2026-07-08 17:56:18 +00:00
[SaveDskF] Add LZMW decompression support. Fixes #108
This commit is contained in:
@@ -64,6 +64,7 @@
|
||||
<Compile Include="Lh5Stream.cs"/>
|
||||
<Compile Include="LhaStream.cs"/>
|
||||
<Compile Include="LzdStream.cs"/>
|
||||
<Compile Include="Lzmw.cs"/>
|
||||
<Compile Include="LZ4.cs"/>
|
||||
<Compile Include="LZFSE.cs"/>
|
||||
<Compile Include="LZIP.cs"/>
|
||||
|
||||
283
Aaru.Compression/Lzmw.cs
Normal file
283
Aaru.Compression/Lzmw.cs
Normal file
@@ -0,0 +1,283 @@
|
||||
// /***************************************************************************
|
||||
// Aaru Data Preservation Suite
|
||||
// ----------------------------------------------------------------------------
|
||||
//
|
||||
// Filename : Lzmw.cs
|
||||
// Author(s) : Natalia Portillo <claunia@claunia.com>
|
||||
//
|
||||
// Component : Compression algorithms.
|
||||
//
|
||||
// --[ Description ] ----------------------------------------------------------
|
||||
//
|
||||
// Decompresses LZMW (Lempel-Ziv-Miller-Wegman) as used by IBM's SaveDskF
|
||||
// compressed ('Z' variant) disk images.
|
||||
//
|
||||
// Based on code from fdimg (img_dskf / dsk_lzmw) by Michal Necasek,
|
||||
// Copyright (c) 2013-2026 Michal Necasek, distributed under the MIT
|
||||
// license. Translated from C to C# for the Aaru Data Preservation Suite.
|
||||
//
|
||||
// --[ 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
|
||||
// Copyright © 2013-2026 Michal Necasek (original C implementation)
|
||||
// ****************************************************************************/
|
||||
|
||||
using System;
|
||||
|
||||
namespace Aaru.Compression;
|
||||
|
||||
/// <summary>
|
||||
/// Implements LZMW (Lempel-Ziv-Miller-Wegman) decompression as used by IBM's SaveDskF compressed disk images.
|
||||
/// Based on the reference C implementation in fdimg (<c>dsk_lzmw.c</c>) by Michal Necasek.
|
||||
/// </summary>
|
||||
public static class Lzmw
|
||||
{
|
||||
const int DICT_SIZE = 4096;
|
||||
|
||||
/// <summary>Set to <c>true</c> if this algorithm is supported, <c>false</c> otherwise.</summary>
|
||||
public static bool IsSupported => true;
|
||||
|
||||
/// <summary>Decodes a buffer compressed with LZMW as used by SaveDskF.</summary>
|
||||
/// <param name="source">Encoded buffer.</param>
|
||||
/// <param name="destination">Buffer where to write the decoded data.</param>
|
||||
/// <returns>The number of decoded bytes, or <c>-1</c> on decompression failure.</returns>
|
||||
public static int DecodeBuffer(byte[] source, byte[] destination)
|
||||
{
|
||||
if(source == null || destination == null) return -1;
|
||||
|
||||
var dict = new DictEntry[DICT_SIZE];
|
||||
|
||||
// Initialize the dictionary (equivalent to lzmw_dict_init).
|
||||
dict[0].Str = null;
|
||||
dict[0].Len = 0;
|
||||
dict[0].Next = 0;
|
||||
dict[0].Prev = 0;
|
||||
dict[0].Ancestor = 0;
|
||||
|
||||
for(var i = 1; i <= 256; i++)
|
||||
{
|
||||
dict[i].Str = new byte[1];
|
||||
dict[i].Str[0] = (byte)(i - 1);
|
||||
dict[i].Len = 1;
|
||||
dict[i].Next = -1;
|
||||
dict[i].Prev = 0;
|
||||
dict[i].Ancestor = 0;
|
||||
}
|
||||
|
||||
for(var i = 257; i < DICT_SIZE - 1; i++) dict[i].Next = (short)(i + 1);
|
||||
|
||||
dict[DICT_SIZE - 1].Next = -1;
|
||||
|
||||
var avail = 257;
|
||||
var readTwo = true;
|
||||
var nibble = 0;
|
||||
var inPos = 0;
|
||||
var outPos = 0;
|
||||
|
||||
// The first code is output but not added to the dictionary.
|
||||
int prevCode = ReadNextCode(source, ref inPos, ref readTwo, ref nibble);
|
||||
|
||||
if(prevCode < 0) return -1;
|
||||
|
||||
int produced = DictOut(dict, prevCode, destination, outPos);
|
||||
|
||||
if(produced < 0) return -1;
|
||||
|
||||
outPos += produced;
|
||||
|
||||
// Process the remaining codes.
|
||||
while(true)
|
||||
{
|
||||
int code = ReadNextCode(source, ref inPos, ref readTwo, ref nibble);
|
||||
|
||||
if(code < 0) break; // End of input.
|
||||
if(code == 0) break; // End marker.
|
||||
|
||||
if(!DictAdd(dict, ref avail, code, prevCode)) return -1;
|
||||
|
||||
produced = DictOut(dict, code, destination, outPos);
|
||||
|
||||
if(produced < 0) return -1;
|
||||
|
||||
prevCode = code;
|
||||
outPos += produced;
|
||||
}
|
||||
|
||||
return outPos;
|
||||
}
|
||||
|
||||
/// <summary>Read the next 12-bit code from the compressed stream.</summary>
|
||||
static int ReadNextCode(byte[] src, ref int pos, ref bool readTwo, ref int nibble)
|
||||
{
|
||||
int code;
|
||||
|
||||
if(readTwo)
|
||||
{
|
||||
// Read the first 16 bits of a code tuple (big-endian in the stream).
|
||||
if(pos + 2 > src.Length) return -1;
|
||||
|
||||
int tw = src[pos] << 8 | src[pos + 1];
|
||||
pos += 2;
|
||||
nibble = tw & 0x0F;
|
||||
code = tw >> 4;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Read the final 8 bits of a code tuple.
|
||||
if(pos + 1 > src.Length) return -1;
|
||||
|
||||
int tb = src[pos++];
|
||||
code = nibble << 8 | tb;
|
||||
}
|
||||
|
||||
readTwo = !readTwo;
|
||||
|
||||
return code;
|
||||
}
|
||||
|
||||
/// <summary>Emit the string associated with <paramref name="code" /> to the destination buffer.</summary>
|
||||
static int DictOut(DictEntry[] dict, int code, byte[] dst, int dstPos)
|
||||
{
|
||||
if(code is <= 0 or >= DICT_SIZE) return -1;
|
||||
|
||||
byte[] str = dict[code].Str;
|
||||
int len = dict[code].Len;
|
||||
|
||||
if(str == null || len == 0) return -1;
|
||||
|
||||
if(dstPos + len > dst.Length) return -1;
|
||||
|
||||
Buffer.BlockCopy(str, 0, dst, dstPos, len);
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
/// <summary>Raise the reference count for a dictionary entry.</summary>
|
||||
static void DictAddRef(DictEntry[] dict, int entry)
|
||||
{
|
||||
if(entry == 0) return;
|
||||
|
||||
// For (Next < 0), reference count is -Next.
|
||||
if(dict[entry].Next < 0)
|
||||
{
|
||||
// Increase reference count.
|
||||
dict[entry].Next--;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Remove entry from the unreferenced (LRU) list.
|
||||
int k = dict[entry].Next;
|
||||
int l = dict[entry].Prev;
|
||||
dict[k].Prev = (short)l;
|
||||
dict[l].Next = (short)k;
|
||||
dict[entry].Next = -1; // Set reference count to one.
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Move an entry to the unreferenced (LRU) queue.</summary>
|
||||
static void DictMoveToUnref(DictEntry[] dict, int entry)
|
||||
{
|
||||
int k = dict[0].Prev;
|
||||
dict[entry].Prev = (short)k;
|
||||
dict[k].Next = (short)entry;
|
||||
dict[entry].Next = 0;
|
||||
dict[0].Prev = (short)entry;
|
||||
}
|
||||
|
||||
/// <summary>Lower the reference count for a dictionary entry.</summary>
|
||||
static void DictRemoveRef(DictEntry[] dict, int entry)
|
||||
{
|
||||
if(entry == 0) return;
|
||||
|
||||
// Decrease reference count.
|
||||
dict[entry].Next++;
|
||||
|
||||
// If no longer referenced, move to the LRU queue.
|
||||
if(dict[entry].Next == 0) DictMoveToUnref(dict, entry);
|
||||
}
|
||||
|
||||
/// <summary>Obtain the next available slot in the dictionary.</summary>
|
||||
static int DictGetAvailSlot(DictEntry[] dict, ref int avail)
|
||||
{
|
||||
int k;
|
||||
|
||||
if(avail != -1)
|
||||
{
|
||||
// An unused entry is available.
|
||||
k = avail;
|
||||
avail = dict[k].Next;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Recycle the least recently used entry.
|
||||
if(dict[0].Prev == dict[0].Next) return -1;
|
||||
|
||||
k = dict[0].Next;
|
||||
int l = dict[k].Next;
|
||||
|
||||
if(k <= 256 || l <= 256) return -1;
|
||||
|
||||
dict[0].Next = (short)l;
|
||||
dict[l].Prev = 0;
|
||||
DictRemoveRef(dict, dict[k].Ancestor);
|
||||
dict[k].Str = null;
|
||||
}
|
||||
|
||||
return k;
|
||||
}
|
||||
|
||||
/// <summary>Add a new dictionary entry formed by <paramref name="prevCode" /> + first byte of <paramref name="code" />.</summary>
|
||||
static bool DictAdd(DictEntry[] dict, ref int avail, int code, int prevCode)
|
||||
{
|
||||
if(code is 0 or >= DICT_SIZE) return false;
|
||||
if(prevCode is 0 or >= DICT_SIZE) return false;
|
||||
|
||||
int j = DictGetAvailSlot(dict, ref avail);
|
||||
|
||||
if(j is <= 256 or >= DICT_SIZE) return false;
|
||||
|
||||
int pLen = dict[prevCode].Len;
|
||||
|
||||
if(pLen == 0) return false;
|
||||
|
||||
int srcCode = code;
|
||||
|
||||
// The "cScSc" case: the newly-allocated slot is the same as the current code.
|
||||
if(j == srcCode) srcCode = prevCode;
|
||||
|
||||
if(dict[srcCode].Str == null || dict[srcCode].Len == 0) return false;
|
||||
|
||||
dict[j].Len = pLen + 1;
|
||||
dict[j].Str = new byte[dict[j].Len];
|
||||
DictAddRef(dict, prevCode);
|
||||
DictMoveToUnref(dict, j);
|
||||
dict[j].Ancestor = (short)prevCode;
|
||||
Buffer.BlockCopy(dict[prevCode].Str, 0, dict[j].Str, 0, pLen);
|
||||
dict[j].Str[pLen] = dict[srcCode].Str[0];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
struct DictEntry
|
||||
{
|
||||
public byte[] Str; // String associated with this code.
|
||||
public int Len; // Length of the string.
|
||||
public short Next; // Next LRU queue entry (or negated refcount when < 0).
|
||||
public short Prev; // Previous LRU queue entry.
|
||||
public short Ancestor; // Parent entry (sans last character).
|
||||
}
|
||||
}
|
||||
@@ -37,6 +37,7 @@ using System.Text;
|
||||
using Aaru.CommonTypes;
|
||||
using Aaru.CommonTypes.Enums;
|
||||
using Aaru.CommonTypes.Interfaces;
|
||||
using Aaru.Compression;
|
||||
using Aaru.Helpers;
|
||||
using Aaru.Logging;
|
||||
|
||||
@@ -57,25 +58,7 @@ public sealed partial class SaveDskF
|
||||
stream.EnsureRead(hdr, 0, 40);
|
||||
_header = Marshal.ByteArrayToStructureLittleEndian<Header>(hdr);
|
||||
|
||||
AaruLogging.Debug(MODULE_NAME, "header.magic = 0x{0:X4}", _header.magic);
|
||||
AaruLogging.Debug(MODULE_NAME, "header.mediaType = 0x{0:X2}", _header.mediaType);
|
||||
AaruLogging.Debug(MODULE_NAME, "header.sectorSize = {0}", _header.sectorSize);
|
||||
AaruLogging.Debug(MODULE_NAME, "header.clusterMask = {0}", _header.clusterMask);
|
||||
AaruLogging.Debug(MODULE_NAME, "header.clusterShift = {0}", _header.clusterShift);
|
||||
AaruLogging.Debug(MODULE_NAME, "header.reservedSectors = {0}", _header.reservedSectors);
|
||||
AaruLogging.Debug(MODULE_NAME, "header.fatCopies = {0}", _header.fatCopies);
|
||||
AaruLogging.Debug(MODULE_NAME, "header.rootEntries = {0}", _header.rootEntries);
|
||||
AaruLogging.Debug(MODULE_NAME, "header.firstCluster = {0}", _header.firstCluster);
|
||||
AaruLogging.Debug(MODULE_NAME, "header.clustersCopied = {0}", _header.clustersCopied);
|
||||
AaruLogging.Debug(MODULE_NAME, "header.sectorsPerFat = {0}", _header.sectorsPerFat);
|
||||
AaruLogging.Debug(MODULE_NAME, "header.checksum = 0x{0:X8}", _header.checksum);
|
||||
AaruLogging.Debug(MODULE_NAME, "header.cylinders = {0}", _header.cylinders);
|
||||
AaruLogging.Debug(MODULE_NAME, "header.heads = {0}", _header.heads);
|
||||
AaruLogging.Debug(MODULE_NAME, "header.sectorsPerTrack = {0}", _header.sectorsPerTrack);
|
||||
AaruLogging.Debug(MODULE_NAME, "header.padding = {0}", _header.padding);
|
||||
AaruLogging.Debug(MODULE_NAME, "header.sectorsCopied = {0}", _header.sectorsCopied);
|
||||
AaruLogging.Debug(MODULE_NAME, "header.commentOffset = {0}", _header.commentOffset);
|
||||
AaruLogging.Debug(MODULE_NAME, "header.dataOffset = {0}", _header.dataOffset);
|
||||
LogHeader();
|
||||
|
||||
if(_header is { dataOffset: 0, magic: SDF_MAGIC_OLD }) _header.dataOffset = 512;
|
||||
|
||||
@@ -85,23 +68,6 @@ public sealed partial class SaveDskF
|
||||
|
||||
if(cmt.Length > 1) _imageInfo.Comments = StringHandlers.CToString(cmt, Encoding.GetEncoding("ibm437"));
|
||||
|
||||
_calculatedChk = 0;
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
int b;
|
||||
|
||||
do
|
||||
{
|
||||
b = stream.ReadByte();
|
||||
|
||||
if(b >= 0) _calculatedChk += (uint)b;
|
||||
} while(b >= 0);
|
||||
|
||||
AaruLogging.Debug(MODULE_NAME,
|
||||
Localization.Calculated_checksum_equals_0_X8_1,
|
||||
_calculatedChk,
|
||||
_calculatedChk == _header.checksum);
|
||||
|
||||
_imageInfo.Application = "SaveDskF";
|
||||
_imageInfo.CreationTime = imageFilter.CreationTime;
|
||||
_imageInfo.LastModificationTime = imageFilter.LastWriteTime;
|
||||
@@ -120,18 +86,13 @@ public sealed partial class SaveDskF
|
||||
if(!string.IsNullOrEmpty(_imageInfo.Comments))
|
||||
AaruLogging.Verbose(Localization.SaveDskF_comments_0, _imageInfo.Comments);
|
||||
|
||||
// TODO: Support compressed images
|
||||
if(_header.magic == SDF_MAGIC_COMPRESSED)
|
||||
{
|
||||
AaruLogging.Error(Localization.Compressed_SaveDskF_images_are_not_supported);
|
||||
|
||||
return ErrorNumber.NotSupported;
|
||||
}
|
||||
|
||||
// SaveDskF only omits ending clusters, leaving no gaps behind, so reading all data we have...
|
||||
stream.Seek(_header.dataOffset, SeekOrigin.Begin);
|
||||
_decodedDisk = new byte[_imageInfo.Sectors * _imageInfo.SectorSize];
|
||||
stream.EnsureRead(_decodedDisk, 0, (int)(stream.Length - _header.dataOffset));
|
||||
|
||||
ErrorNumber loadError = _header.magic == SDF_MAGIC_COMPRESSED
|
||||
? LoadCompressed(stream)
|
||||
: LoadUncompressed(stream);
|
||||
|
||||
if(loadError != ErrorNumber.NoError) return loadError;
|
||||
|
||||
_imageInfo.Cylinders = _header.cylinders;
|
||||
_imageInfo.Heads = _header.heads;
|
||||
@@ -140,6 +101,95 @@ public sealed partial class SaveDskF
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
|
||||
void LogHeader()
|
||||
{
|
||||
AaruLogging.Debug(MODULE_NAME, "header.magic = 0x{0:X4}", _header.magic);
|
||||
AaruLogging.Debug(MODULE_NAME, "header.mediaType = 0x{0:X2}", _header.mediaType);
|
||||
AaruLogging.Debug(MODULE_NAME, "header.sectorSize = {0}", _header.sectorSize);
|
||||
AaruLogging.Debug(MODULE_NAME, "header.clusterMask = {0}", _header.clusterMask);
|
||||
AaruLogging.Debug(MODULE_NAME, "header.clusterShift = {0}", _header.clusterShift);
|
||||
AaruLogging.Debug(MODULE_NAME, "header.reservedSectors = {0}", _header.reservedSectors);
|
||||
AaruLogging.Debug(MODULE_NAME, "header.fatCopies = {0}", _header.fatCopies);
|
||||
AaruLogging.Debug(MODULE_NAME, "header.rootEntries = {0}", _header.rootEntries);
|
||||
AaruLogging.Debug(MODULE_NAME, "header.firstCluster = {0}", _header.firstCluster);
|
||||
AaruLogging.Debug(MODULE_NAME, "header.clustersCopied = {0}", _header.clustersCopied);
|
||||
AaruLogging.Debug(MODULE_NAME, "header.sectorsPerFat = {0}", _header.sectorsPerFat);
|
||||
AaruLogging.Debug(MODULE_NAME, "header.checksum = 0x{0:X8}", _header.checksum);
|
||||
AaruLogging.Debug(MODULE_NAME, "header.cylinders = {0}", _header.cylinders);
|
||||
AaruLogging.Debug(MODULE_NAME, "header.heads = {0}", _header.heads);
|
||||
AaruLogging.Debug(MODULE_NAME, "header.sectorsPerTrack = {0}", _header.sectorsPerTrack);
|
||||
AaruLogging.Debug(MODULE_NAME, "header.padding = {0}", _header.padding);
|
||||
AaruLogging.Debug(MODULE_NAME, "header.sectorsCopied = {0}", _header.sectorsCopied);
|
||||
AaruLogging.Debug(MODULE_NAME, "header.commentOffset = {0}", _header.commentOffset);
|
||||
AaruLogging.Debug(MODULE_NAME, "header.dataOffset = {0}", _header.dataOffset);
|
||||
}
|
||||
|
||||
ErrorNumber LoadCompressed(Stream stream)
|
||||
{
|
||||
long compressedLength = stream.Length - _header.dataOffset;
|
||||
|
||||
if(compressedLength <= 0) return ErrorNumber.InvalidArgument;
|
||||
|
||||
var compressed = new byte[compressedLength];
|
||||
stream.Seek(_header.dataOffset, SeekOrigin.Begin);
|
||||
stream.EnsureRead(compressed, 0, compressed.Length);
|
||||
|
||||
// The image stores `sectorsCopied` sectors (the trailing unallocated clusters are omitted).
|
||||
int storedBytes = _header.sectorsCopied * _header.sectorSize;
|
||||
var decompressed = new byte[storedBytes];
|
||||
|
||||
int produced = Lzmw.DecodeBuffer(compressed, decompressed);
|
||||
|
||||
if(produced != storedBytes)
|
||||
{
|
||||
AaruLogging.Error(Localization.Compressed_SaveDskF_images_are_not_supported);
|
||||
|
||||
return ErrorNumber.InvalidArgument;
|
||||
}
|
||||
|
||||
// Validate the decompressed data against the stored 16-bit word checksum (fdimg dskf_calc_csum).
|
||||
uint cksum = 0;
|
||||
|
||||
for(var i = 0; i + 1 < decompressed.Length; i += 2) cksum += (uint)(decompressed[i] | decompressed[i + 1] << 8);
|
||||
|
||||
_calculatedChk = cksum;
|
||||
|
||||
AaruLogging.Debug(MODULE_NAME,
|
||||
Localization.Calculated_checksum_equals_0_X8_1,
|
||||
_calculatedChk,
|
||||
_calculatedChk == _header.checksum);
|
||||
|
||||
Array.Copy(decompressed, 0, _decodedDisk, 0, storedBytes);
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
|
||||
ErrorNumber LoadUncompressed(Stream stream)
|
||||
{
|
||||
_calculatedChk = 0;
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
int b;
|
||||
|
||||
do
|
||||
{
|
||||
b = stream.ReadByte();
|
||||
|
||||
if(b >= 0) _calculatedChk += (uint)b;
|
||||
} while(b >= 0);
|
||||
|
||||
AaruLogging.Debug(MODULE_NAME,
|
||||
Localization.Calculated_checksum_equals_0_X8_1,
|
||||
_calculatedChk,
|
||||
_calculatedChk == _header.checksum);
|
||||
|
||||
// SaveDskF only omits ending clusters, leaving no gaps behind, so reading all data we have...
|
||||
stream.Seek(_header.dataOffset, SeekOrigin.Begin);
|
||||
stream.EnsureRead(_decodedDisk, 0, (int)(stream.Length - _header.dataOffset));
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ErrorNumber ReadSector(ulong sectorAddress, bool negative, out byte[] buffer, out SectorStatus sectorStatus)
|
||||
{
|
||||
|
||||
263
Aaru.Tests/Images/SaveDskFCompressed.cs
Normal file
263
Aaru.Tests/Images/SaveDskFCompressed.cs
Normal file
@@ -0,0 +1,263 @@
|
||||
// /***************************************************************************
|
||||
// Aaru Data Preservation Suite
|
||||
// ----------------------------------------------------------------------------
|
||||
//
|
||||
// Filename : SaveDskFCompressed.cs
|
||||
// Author(s) : Natalia Portillo <claunia@claunia.com>
|
||||
//
|
||||
// Component : Aaru unit testing.
|
||||
//
|
||||
// --[ License ] --------------------------------------------------------------
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as
|
||||
// published by the Free Software Foundation, either version 3 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This program 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 General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
// ----------------------------------------------------------------------------
|
||||
// Copyright © 2011-2026 Natalia Portillo
|
||||
// ****************************************************************************/
|
||||
|
||||
using System.IO;
|
||||
using Aaru.CommonTypes;
|
||||
using Aaru.CommonTypes.Interfaces;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace Aaru.Tests.Images;
|
||||
|
||||
[TestFixture]
|
||||
public class SaveDskFCompressed : BlockMediaImageTest
|
||||
{
|
||||
public override string DataFolder =>
|
||||
Path.Combine(Consts.TestFilesRoot, "Media image formats", "SaveDskF", "compressed");
|
||||
|
||||
public override IMediaImage Plugin => new Aaru.Images.SaveDskF();
|
||||
|
||||
public override BlockImageTestExpected[] Tests =>
|
||||
[
|
||||
new()
|
||||
{
|
||||
TestFile = "5dd8_a.dsk",
|
||||
MediaType = MediaType.DOS_525_DS_DD_8,
|
||||
Sectors = 640,
|
||||
SectorSize = 512,
|
||||
Md5 = "4989762c82f173f9b52e0bdb8cf5becb"
|
||||
},
|
||||
new()
|
||||
{
|
||||
TestFile = "5dd8_ak.dsk",
|
||||
MediaType = MediaType.DOS_525_DS_DD_8,
|
||||
Sectors = 640,
|
||||
SectorSize = 512,
|
||||
Md5 = "4989762c82f173f9b52e0bdb8cf5becb"
|
||||
},
|
||||
new()
|
||||
{
|
||||
TestFile = "5dd8defs.dsk",
|
||||
MediaType = MediaType.DOS_525_DS_DD_8,
|
||||
Sectors = 640,
|
||||
SectorSize = 512,
|
||||
Md5 = "5a1e0a75d31d88c1ce7429fd333c268f"
|
||||
},
|
||||
new()
|
||||
{
|
||||
TestFile = "5dd_a.dsk",
|
||||
MediaType = MediaType.DOS_525_DS_DD_9,
|
||||
Sectors = 720,
|
||||
SectorSize = 512,
|
||||
Md5 = "8a4d35dd0d97e6bca8b000170a43a56f"
|
||||
},
|
||||
new()
|
||||
{
|
||||
TestFile = "5dd_ak.dsk",
|
||||
MediaType = MediaType.DOS_525_DS_DD_9,
|
||||
Sectors = 720,
|
||||
SectorSize = 512,
|
||||
Md5 = "8a4d35dd0d97e6bca8b000170a43a56f"
|
||||
},
|
||||
new()
|
||||
{
|
||||
TestFile = "5dddefs.dsk",
|
||||
MediaType = MediaType.DOS_525_DS_DD_9,
|
||||
Sectors = 720,
|
||||
SectorSize = 512,
|
||||
Md5 = "c1a67b27bc76b64d0845965501b24120"
|
||||
},
|
||||
new()
|
||||
{
|
||||
TestFile = "5hd_a.dsk",
|
||||
MediaType = MediaType.DOS_525_HD,
|
||||
Sectors = 2400,
|
||||
SectorSize = 512,
|
||||
Md5 = "2ce745ac23712d3eb03d7a11ba933b12"
|
||||
},
|
||||
new()
|
||||
{
|
||||
TestFile = "5hd_ak.dsk",
|
||||
MediaType = MediaType.DOS_525_HD,
|
||||
Sectors = 2400,
|
||||
SectorSize = 512,
|
||||
Md5 = "2ce745ac23712d3eb03d7a11ba933b12"
|
||||
},
|
||||
new()
|
||||
{
|
||||
TestFile = "5hddefs.dsk",
|
||||
MediaType = MediaType.DOS_525_HD,
|
||||
Sectors = 2400,
|
||||
SectorSize = 512,
|
||||
Md5 = "1c28b4c3cdc1dbf19c24a5eca3891a87"
|
||||
},
|
||||
new()
|
||||
{
|
||||
TestFile = "5sd8_a.dsk",
|
||||
MediaType = MediaType.DOS_525_SS_DD_8,
|
||||
Sectors = 320,
|
||||
SectorSize = 512,
|
||||
Md5 = "6f5d09c13a7b481bad9ea78042e61e00"
|
||||
},
|
||||
new()
|
||||
{
|
||||
TestFile = "5sd8_ak.dsk",
|
||||
MediaType = MediaType.DOS_525_SS_DD_8,
|
||||
Sectors = 320,
|
||||
SectorSize = 512,
|
||||
Md5 = "6f5d09c13a7b481bad9ea78042e61e00"
|
||||
},
|
||||
new()
|
||||
{
|
||||
TestFile = "5sd8defs.dsk",
|
||||
MediaType = MediaType.DOS_525_SS_DD_8,
|
||||
Sectors = 320,
|
||||
SectorSize = 512,
|
||||
Md5 = "65ce0cd08d90c882df12637c9c72c1ba"
|
||||
},
|
||||
new()
|
||||
{
|
||||
TestFile = "5sd_a.dsk",
|
||||
MediaType = MediaType.DOS_525_SS_DD_9,
|
||||
Sectors = 360,
|
||||
SectorSize = 512,
|
||||
Md5 = "fd81fceb26bda5b02053c5c729a6f67f"
|
||||
},
|
||||
new()
|
||||
{
|
||||
TestFile = "5sd_ak.dsk",
|
||||
MediaType = MediaType.DOS_525_SS_DD_9,
|
||||
Sectors = 360,
|
||||
SectorSize = 512,
|
||||
Md5 = "fd81fceb26bda5b02053c5c729a6f67f"
|
||||
},
|
||||
new()
|
||||
{
|
||||
TestFile = "5sddefs.dsk",
|
||||
MediaType = MediaType.DOS_525_SS_DD_9,
|
||||
Sectors = 360,
|
||||
SectorSize = 512,
|
||||
Md5 = "412fdc582506c0d7e76735d403b30759"
|
||||
},
|
||||
new()
|
||||
{
|
||||
TestFile = "mf2dd_a.dsk",
|
||||
MediaType = MediaType.DOS_35_DS_DD_9,
|
||||
Sectors = 1440,
|
||||
SectorSize = 512,
|
||||
Md5 = "e574be0d057f2ef775dfb685561d27cf"
|
||||
},
|
||||
new()
|
||||
{
|
||||
TestFile = "mf2dd_ak.dsk",
|
||||
MediaType = MediaType.DOS_35_DS_DD_9,
|
||||
Sectors = 1440,
|
||||
SectorSize = 512,
|
||||
Md5 = "e574be0d057f2ef775dfb685561d27cf"
|
||||
},
|
||||
new()
|
||||
{
|
||||
TestFile = "mf2dd_defs.dsk",
|
||||
MediaType = MediaType.DOS_35_DS_DD_9,
|
||||
Sectors = 1440,
|
||||
SectorSize = 512,
|
||||
Md5 = "2aefc1e97f29bf9982e0fd7091dfb9f5"
|
||||
},
|
||||
new()
|
||||
{
|
||||
TestFile = "mf2ed_a.dsk",
|
||||
MediaType = MediaType.ECMA_147,
|
||||
Sectors = 5760,
|
||||
SectorSize = 512,
|
||||
Md5 = "42e73287b23ac985c9825466cae26859"
|
||||
},
|
||||
new()
|
||||
{
|
||||
TestFile = "mf2ed_ak.dsk",
|
||||
MediaType = MediaType.ECMA_147,
|
||||
Sectors = 5760,
|
||||
SectorSize = 512,
|
||||
Md5 = "42e73287b23ac985c9825466cae26859"
|
||||
},
|
||||
new()
|
||||
{
|
||||
TestFile = "mf2ed_defs.dsk",
|
||||
MediaType = MediaType.ECMA_147,
|
||||
Sectors = 5760,
|
||||
SectorSize = 512,
|
||||
Md5 = "e4746aa9629a2325c520db1c8a641ac6"
|
||||
},
|
||||
new()
|
||||
{
|
||||
TestFile = "mf2hd_a.dsk",
|
||||
MediaType = MediaType.DOS_35_HD,
|
||||
Sectors = 2880,
|
||||
SectorSize = 512,
|
||||
Md5 = "009cc68e28b2b13814d3afbec9d9e59f"
|
||||
},
|
||||
new()
|
||||
{
|
||||
TestFile = "mf2hd_ak.dsk",
|
||||
MediaType = MediaType.DOS_35_HD,
|
||||
Sectors = 2880,
|
||||
SectorSize = 512,
|
||||
Md5 = "009cc68e28b2b13814d3afbec9d9e59f"
|
||||
},
|
||||
new()
|
||||
{
|
||||
TestFile = "mf2hd_defs.dsk",
|
||||
MediaType = MediaType.DOS_35_HD,
|
||||
Sectors = 2880,
|
||||
SectorSize = 512,
|
||||
Md5 = "003e9130d83a23018f488f9fa89cae5e"
|
||||
},
|
||||
new()
|
||||
{
|
||||
TestFile = "mf2hd_xdf_a.dsk",
|
||||
MediaType = MediaType.XDF_35,
|
||||
Sectors = 3680,
|
||||
SectorSize = 512,
|
||||
Md5 = "34b4bdab5fcc17076cceb7c1a39ea430"
|
||||
},
|
||||
new()
|
||||
{
|
||||
TestFile = "mf2hd_xdf_ak.dsk",
|
||||
MediaType = MediaType.XDF_35,
|
||||
Sectors = 3680,
|
||||
SectorSize = 512,
|
||||
Md5 = "34b4bdab5fcc17076cceb7c1a39ea430"
|
||||
},
|
||||
new()
|
||||
{
|
||||
TestFile = "mf2hd_xdf_defs.dsk",
|
||||
MediaType = MediaType.XDF_35,
|
||||
Sectors = 3680,
|
||||
SectorSize = 512,
|
||||
Md5 = "2770e5b1b7935ca6e9695a32008b936a"
|
||||
}
|
||||
];
|
||||
}
|
||||
Reference in New Issue
Block a user