mirror of
https://github.com/aaru-dps/Aaru.git
synced 2026-05-21 12:07:58 +00:00
[QRST] Add support for QRST V5 (PKWARE-compressed) images with native decompression
This commit is contained in:
2117
Aaru.Images/Localization/Localization.Designer.cs
generated
2117
Aaru.Images/Localization/Localization.Designer.cs
generated
File diff suppressed because it is too large
Load Diff
@@ -3153,6 +3153,12 @@
|
||||
<data name="Qrst_V5_images_are_not_supported" xml:space="preserve">
|
||||
<value>Las imágenes QRST V5 (comprimidas con PKWARE) no están soportadas.</value>
|
||||
</data>
|
||||
<data name="Qrst_V5_requires_native_decompressor" xml:space="preserve">
|
||||
<value>Las imágenes QRST V5 (comprimidas con PKWARE) requieren la biblioteca nativa de descompresión.</value>
|
||||
</data>
|
||||
<data name="Qrst_V5_decompression_yielded_incomplete_data" xml:space="preserve">
|
||||
<value>La descompresión de la imagen QRST V5 produjo datos incompletos.</value>
|
||||
</data>
|
||||
<data name="Qrst_track_decompression_yielded_incomplete_data" xml:space="preserve">
|
||||
<value>La descompresión de la pista QRST produjo datos incompletos.</value>
|
||||
</data>
|
||||
|
||||
@@ -3172,6 +3172,12 @@
|
||||
<data name="Qrst_V5_images_are_not_supported" xml:space="preserve">
|
||||
<value>QRST V5 (PKWARE-compressed) images are not supported.</value>
|
||||
</data>
|
||||
<data name="Qrst_V5_requires_native_decompressor" xml:space="preserve">
|
||||
<value>QRST V5 (PKWARE-compressed) images require the native decompression library.</value>
|
||||
</data>
|
||||
<data name="Qrst_V5_decompression_yielded_incomplete_data" xml:space="preserve">
|
||||
<value>QRST V5 decompression yielded incomplete data.</value>
|
||||
</data>
|
||||
<data name="Qrst_track_decompression_yielded_incomplete_data" xml:space="preserve">
|
||||
<value>QRST track decompression yielded incomplete data.</value>
|
||||
</data>
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using Aaru.CommonTypes.Enums;
|
||||
using Aaru.Compression.Zip;
|
||||
using Aaru.Helpers;
|
||||
using Aaru.Logging;
|
||||
|
||||
@@ -42,6 +43,94 @@ namespace Aaru.Images;
|
||||
|
||||
public sealed partial class Qrst
|
||||
{
|
||||
ErrorNumber WalkPreV5Tracks(Stream stream, int headerSize, int totalTracks)
|
||||
{
|
||||
long curOfs = headerSize;
|
||||
var trkHdrBuf = new byte[Marshal.SizeOf<QrstTrackHeader>()];
|
||||
var blkLenBuf = new byte[sizeof(ushort)];
|
||||
|
||||
for(var i = 0; i < totalTracks; i++)
|
||||
{
|
||||
stream.Seek(curOfs, SeekOrigin.Begin);
|
||||
|
||||
if(stream.EnsureRead(trkHdrBuf, 0, trkHdrBuf.Length) != trkHdrBuf.Length)
|
||||
return ErrorNumber.InvalidArgument;
|
||||
|
||||
QrstTrackHeader trkHdr = Marshal.ByteArrayToStructureLittleEndian<QrstTrackHeader>(trkHdrBuf);
|
||||
|
||||
if(trkHdr.cyl > _cyls || trkHdr.head > _heads || trkHdr.type > TRK_CMPRSD)
|
||||
return ErrorNumber.InvalidArgument;
|
||||
|
||||
int trkIdx = trkHdr.cyl * _heads + trkHdr.head;
|
||||
|
||||
if(_trackOffset.ContainsKey(trkIdx)) return ErrorNumber.InvalidArgument;
|
||||
|
||||
_trackOffset[trkIdx] = curOfs;
|
||||
curOfs += trkHdrBuf.Length;
|
||||
|
||||
switch(trkHdr.type)
|
||||
{
|
||||
case TRK_NORMAL:
|
||||
curOfs += _trackLen;
|
||||
|
||||
break;
|
||||
case TRK_BLANK:
|
||||
curOfs += 1;
|
||||
|
||||
break;
|
||||
case TRK_CMPRSD:
|
||||
if(stream.EnsureRead(blkLenBuf, 0, blkLenBuf.Length) != blkLenBuf.Length)
|
||||
return ErrorNumber.InvalidArgument;
|
||||
|
||||
var blkLen = BitConverter.ToUInt16(blkLenBuf, 0);
|
||||
curOfs += blkLenBuf.Length + blkLen;
|
||||
|
||||
break;
|
||||
default:
|
||||
return ErrorNumber.InvalidArgument;
|
||||
}
|
||||
}
|
||||
|
||||
if(stream.Length < curOfs) return ErrorNumber.InvalidArgument;
|
||||
|
||||
if(_trackOffset.Count != totalTracks) return ErrorNumber.InvalidArgument;
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
|
||||
ErrorNumber DecompressV5(Stream stream, int headerSize, long totalBytes)
|
||||
{
|
||||
if(!Blast.IsSupported)
|
||||
{
|
||||
AaruLogging.Error(MODULE_NAME, Localization.Qrst_V5_requires_native_decompressor);
|
||||
|
||||
return ErrorNumber.NotSupported;
|
||||
}
|
||||
|
||||
long payloadLen = stream.Length - headerSize;
|
||||
|
||||
if(payloadLen <= 0 || payloadLen > int.MaxValue) return ErrorNumber.InvalidArgument;
|
||||
|
||||
var compressed = new byte[payloadLen];
|
||||
stream.Seek(headerSize, SeekOrigin.Begin);
|
||||
|
||||
if(stream.EnsureRead(compressed, 0, compressed.Length) != compressed.Length) return ErrorNumber.InOutError;
|
||||
|
||||
var decompressed = new byte[totalBytes];
|
||||
int actual = Blast.DecodeBuffer(compressed, decompressed);
|
||||
|
||||
if(actual != totalBytes)
|
||||
{
|
||||
AaruLogging.Error(MODULE_NAME, Localization.Qrst_V5_decompression_yielded_incomplete_data);
|
||||
|
||||
return ErrorNumber.InOutError;
|
||||
}
|
||||
|
||||
_flatImage = decompressed;
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
|
||||
ErrorNumber ReadTrackIntoCache(Stream stream, int trackNum)
|
||||
{
|
||||
if(!_trackOffset.TryGetValue(trackNum, out long offset)) return ErrorNumber.SectorNotFound;
|
||||
|
||||
@@ -37,7 +37,8 @@
|
||||
* original (pre-V5) version and version 5 (V5).
|
||||
*
|
||||
* QRST V5 contains a disk image compressed with the PKWARE Data Compression
|
||||
* Library and is not supported.
|
||||
* Library (DCL) Implode algorithm, stored as a single compressed stream after
|
||||
* the header that decompresses to a flat sector-sequential raw disk image.
|
||||
*
|
||||
* QRST pre-V5 contains a collection of tracks. Each track may be uncompressed,
|
||||
* blank (with a filler byte), or run-length encoded. Only standard DOS disk
|
||||
@@ -65,6 +66,9 @@ public sealed partial class Qrst : IMediaImage
|
||||
readonly Dictionary<int, long> _trackOffset = new();
|
||||
byte _cyls;
|
||||
|
||||
/// <summary>When non-null, the image is a V5 (PKWARE-compressed) QRST and this holds the decompressed flat disk image.</summary>
|
||||
byte[] _flatImage;
|
||||
|
||||
QrstHeader _header;
|
||||
byte _heads;
|
||||
|
||||
|
||||
@@ -63,14 +63,6 @@ public sealed partial class Qrst
|
||||
|
||||
if(!hdr.signature.SequenceEqual(_signature)) return ErrorNumber.InvalidArgument;
|
||||
|
||||
// V5 (PKWARE-compressed) images are not supported.
|
||||
if(hdr.type != 0)
|
||||
{
|
||||
AaruLogging.Error(MODULE_NAME, Localization.Qrst_V5_images_are_not_supported);
|
||||
|
||||
return ErrorNumber.NotSupported;
|
||||
}
|
||||
|
||||
if(hdr.disk_fmt == 0 || hdr.disk_fmt >= _dskDesc.Length) return ErrorNumber.InvalidArgument;
|
||||
|
||||
(byte cyls, byte heads, byte spt) = _dskDesc[hdr.disk_fmt];
|
||||
@@ -81,60 +73,30 @@ public sealed partial class Qrst
|
||||
_trackLen = spt * SECTOR_SIZE;
|
||||
_header = hdr;
|
||||
|
||||
int totalTracks = _cyls * _heads;
|
||||
int totalTracks = _cyls * _heads;
|
||||
long totalBytes = (long)totalTracks * _trackLen;
|
||||
|
||||
// Walk the track headers to build a lookup table of file offsets.
|
||||
long curOfs = headerSize;
|
||||
var trkHdrBuf = new byte[Marshal.SizeOf<QrstTrackHeader>()];
|
||||
var blkLenBuf = new byte[sizeof(ushort)];
|
||||
|
||||
for(var i = 0; i < totalTracks; i++)
|
||||
switch(hdr.type)
|
||||
{
|
||||
stream.Seek(curOfs, SeekOrigin.Begin);
|
||||
case 0:
|
||||
// Pre-V5: walk the track headers to build a lookup table of file offsets.
|
||||
ErrorNumber walkErr = WalkPreV5Tracks(stream, headerSize, totalTracks);
|
||||
|
||||
if(stream.EnsureRead(trkHdrBuf, 0, trkHdrBuf.Length) != trkHdrBuf.Length)
|
||||
if(walkErr != ErrorNumber.NoError) return walkErr;
|
||||
|
||||
break;
|
||||
case 2:
|
||||
// V5: the remainder of the file is a single PKWARE DCL Implode stream that
|
||||
// decompresses to a raw, sector-sequential flat disk image.
|
||||
ErrorNumber v5Err = DecompressV5(stream, headerSize, totalBytes);
|
||||
|
||||
if(v5Err != ErrorNumber.NoError) return v5Err;
|
||||
|
||||
break;
|
||||
default:
|
||||
return ErrorNumber.InvalidArgument;
|
||||
|
||||
QrstTrackHeader trkHdr = Marshal.ByteArrayToStructureLittleEndian<QrstTrackHeader>(trkHdrBuf);
|
||||
|
||||
if(trkHdr.cyl > _cyls || trkHdr.head > _heads || trkHdr.type > TRK_CMPRSD)
|
||||
return ErrorNumber.InvalidArgument;
|
||||
|
||||
int trkIdx = trkHdr.cyl * _heads + trkHdr.head;
|
||||
|
||||
// Reject duplicates.
|
||||
if(_trackOffset.ContainsKey(trkIdx)) return ErrorNumber.InvalidArgument;
|
||||
|
||||
_trackOffset[trkIdx] = curOfs;
|
||||
curOfs += trkHdrBuf.Length;
|
||||
|
||||
switch(trkHdr.type)
|
||||
{
|
||||
case TRK_NORMAL:
|
||||
curOfs += _trackLen;
|
||||
|
||||
break;
|
||||
case TRK_BLANK:
|
||||
curOfs += 1;
|
||||
|
||||
break;
|
||||
case TRK_CMPRSD:
|
||||
if(stream.EnsureRead(blkLenBuf, 0, blkLenBuf.Length) != blkLenBuf.Length)
|
||||
return ErrorNumber.InvalidArgument;
|
||||
|
||||
var blkLen = BitConverter.ToUInt16(blkLenBuf, 0);
|
||||
curOfs += blkLenBuf.Length + blkLen;
|
||||
|
||||
break;
|
||||
default:
|
||||
return ErrorNumber.InvalidArgument;
|
||||
}
|
||||
}
|
||||
|
||||
if(stream.Length < curOfs) return ErrorNumber.InvalidArgument;
|
||||
|
||||
if(_trackOffset.Count != totalTracks) return ErrorNumber.InvalidArgument;
|
||||
|
||||
_imageInfo.Cylinders = _cyls;
|
||||
_imageInfo.Heads = _heads;
|
||||
_imageInfo.SectorsPerTrack = _spt;
|
||||
@@ -170,6 +132,16 @@ public sealed partial class Qrst
|
||||
|
||||
if(sectorAddress >= _imageInfo.Sectors) return ErrorNumber.OutOfRange;
|
||||
|
||||
buffer = new byte[SECTOR_SIZE];
|
||||
|
||||
if(_flatImage != null)
|
||||
{
|
||||
Array.Copy(_flatImage, (long)sectorAddress * SECTOR_SIZE, buffer, 0, SECTOR_SIZE);
|
||||
sectorStatus = SectorStatus.Dumped;
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
|
||||
var trackNum = (int)(sectorAddress / _spt);
|
||||
var sectorInTrk = (int)(sectorAddress % _spt);
|
||||
|
||||
@@ -182,7 +154,6 @@ public sealed partial class Qrst
|
||||
trackData = _trackCache[trackNum];
|
||||
}
|
||||
|
||||
buffer = new byte[SECTOR_SIZE];
|
||||
Array.Copy(trackData, sectorInTrk * SECTOR_SIZE, buffer, 0, SECTOR_SIZE);
|
||||
|
||||
sectorStatus = SectorStatus.Dumped;
|
||||
|
||||
Reference in New Issue
Block a user