diff --git a/Aaru.Images/PartClone/Constants.cs b/Aaru.Images/PartClone/Constants.cs
index 521df2e09..ea3f92120 100644
--- a/Aaru.Images/PartClone/Constants.cs
+++ b/Aaru.Images/PartClone/Constants.cs
@@ -34,7 +34,20 @@ namespace Aaru.Images;
public sealed partial class PartClone
{
- const int CRC_SIZE = 4;
+ /// Regular size of the per-block CRC32 trailer in partclone v0001.
+ const int CRC_SIZE_NORMAL = 4;
+ ///
+ /// Size of the per-block CRC32 trailer in partclone v0001 images affected by the legacy 64-bit platform bug
+ /// (the CRC was written using sizeof(unsigned long), producing 8 bytes on x86_64 instead of 4).
+ ///
+ const int CRC_SIZE_X64_BUG = 8;
+ /// Polynomial used by partclone's reflected CRC-32 (Ethernet / ISO).
+ const uint CRC32_POLYNOMIAL = 0xEDB88320;
+ /// Initial seed used by partclone's init_crc32().
+ const uint CRC32_SEED = 0xFFFFFFFF;
+ /// Only partclone image format 0001 is supported.
+ const string SUPPORTED_VERSION = "0001";
+
const uint MAX_CACHE_SIZE = 16777216;
const uint MAX_CACHED_SECTORS = MAX_CACHE_SIZE / 512;
readonly byte[] _biTmAgIc = "BiTmAgIc"u8.ToArray();
diff --git a/Aaru.Images/PartClone/Helpers.cs b/Aaru.Images/PartClone/Helpers.cs
index 37efc883b..632e9d4a4 100644
--- a/Aaru.Images/PartClone/Helpers.cs
+++ b/Aaru.Images/PartClone/Helpers.cs
@@ -34,6 +34,60 @@ namespace Aaru.Images;
public sealed partial class PartClone
{
+ ///
+ /// Reflected CRC-32 lookup table (polynomial ) used by partclone's
+ /// crc32_0001() implementation. Kept private to the plugin because that routine carries a source-level
+ /// bug (see ) that would be incorrect for any other CRC-32 consumer.
+ ///
+ static readonly uint[] _crc0001Table = BuildCrc0001Table();
+
+ static uint[] BuildCrc0001Table()
+ {
+ var table = new uint[256];
+
+ for(uint i = 0; i < 256; i++)
+ {
+ uint entry = i;
+
+ for(var j = 0; j < 8; j++)
+ {
+ if((entry & 1) != 0)
+ entry = entry >> 1 ^ CRC32_POLYNOMIAL;
+ else
+ entry >>= 1;
+ }
+
+ table[i] = entry;
+ }
+
+ return table;
+ }
+
+ ///
+ /// Advances a running CRC-32 seed the way partclone's crc32_0001() does for image format 0001. The
+ /// upstream routine has a long-standing bug: the input pointer is never advanced, so every iteration
+ /// folds the very first byte of into the CRC. This quirk is intentionally
+ /// reproduced here so that Aaru's verification matches the checksums actually stored on disk.
+ ///
+ /// Previous cumulative CRC ( at the start of the stream).
+ /// Block buffer; only its first byte is used.
+ /// Number of iterations to run (i.e. the block size in bytes).
+ /// Updated cumulative CRC.
+ static uint UpdateCrc32_0001(uint crc, byte[] buffer, int size)
+ {
+ if(size <= 0 || buffer is null || buffer.Length == 0) return crc;
+
+ byte b = buffer[0];
+
+ for(var s = 0; s < size; s++)
+ {
+ uint tmp = crc ^ b;
+ crc = crc >> 8 ^ _crc0001Table[tmp & 0xFF];
+ }
+
+ return crc;
+ }
+
ulong BlockOffset(ulong sectorAddress)
{
_extents.GetStart(sectorAddress, out ulong extentStart);
diff --git a/Aaru.Images/PartClone/PartClone.cs b/Aaru.Images/PartClone/PartClone.cs
index 39699e7eb..f34f7d323 100644
--- a/Aaru.Images/PartClone/PartClone.cs
+++ b/Aaru.Images/PartClone/PartClone.cs
@@ -46,7 +46,9 @@ public sealed partial class PartClone : IMediaImage, IVerifiableImage
// The used block "bitmap" uses one byte per block
// TODO: Convert on-image bytemap to on-memory bitmap
- byte[] _byteMap;
+ byte[] _byteMap;
+ /// Detected stride of the per-block CRC trailer (4 for normal images, 8 for x64-bug ones).
+ int _crcSize;
long _dataOff;
ExtentsULong _extents;
Dictionary _extentsOff;
diff --git a/Aaru.Images/PartClone/Read.cs b/Aaru.Images/PartClone/Read.cs
index aad8e65a1..b681dbfbb 100644
--- a/Aaru.Images/PartClone/Read.cs
+++ b/Aaru.Images/PartClone/Read.cs
@@ -70,6 +70,17 @@ public sealed partial class PartClone
AaruLogging.Debug(MODULE_NAME, "pHdr.totalBlocks = {0}", _pHdr.totalBlocks);
AaruLogging.Debug(MODULE_NAME, "pHdr.usedBlocks = {0}", _pHdr.usedBlocks);
+ string version = StringHandlers.CToString(_pHdr.version);
+
+ if(version != SUPPORTED_VERSION)
+ {
+ AaruLogging.Error(MODULE_NAME,
+ "Unsupported partclone image version '{0}', only '0001' is supported",
+ version);
+
+ return ErrorNumber.NotSupported;
+ }
+
_byteMap = new byte[_pHdr.totalBlocks];
AaruLogging.Debug(MODULE_NAME, Localization.Reading_bytemap_0_bytes, _byteMap.Length);
stream.EnsureRead(_byteMap, 0, _byteMap.Length);
@@ -89,6 +100,35 @@ public sealed partial class PartClone
_dataOff = stream.Position;
AaruLogging.Debug(MODULE_NAME, "pHdr.dataOff = {0}", _dataOff);
+ // Autodetect the legacy x64 CRC bug: on 64-bit platforms old partclone versions serialized
+ // the 4-byte CRC using sizeof(unsigned long), writing 8 bytes per block instead of 4.
+ // Compare the trailing data region length against both possibilities to pick the right stride.
+ long trailing = stream.Length - _dataOff;
+ var expectedNormal = (long)(_pHdr.usedBlocks * (_pHdr.blockSize + CRC_SIZE_NORMAL));
+ var expectedX64 = (long)(_pHdr.usedBlocks * (_pHdr.blockSize + CRC_SIZE_X64_BUG));
+
+ if(trailing == expectedX64 && trailing != expectedNormal)
+ {
+ _crcSize = CRC_SIZE_X64_BUG;
+
+ AaruLogging.Debug(MODULE_NAME,
+ "Detected partclone v0001 x64 CRC bug: using 8-byte CRC stride (trailing={0})",
+ trailing);
+ }
+ else
+ {
+ _crcSize = CRC_SIZE_NORMAL;
+
+ if(trailing != expectedNormal && _pHdr.usedBlocks > 0)
+ {
+ AaruLogging.Debug(MODULE_NAME,
+ "Partclone data region size {0} does not match either layout (normal={1}, x64={2}); assuming 4-byte CRC stride",
+ trailing,
+ expectedNormal,
+ expectedX64);
+ }
+ }
+
AaruLogging.Debug(MODULE_NAME, Localization.Filling_extents);
var extentFillStopwatch = new Stopwatch();
extentFillStopwatch.Start();
@@ -164,7 +204,7 @@ public sealed partial class PartClone
if(_sectorCache.TryGetValue(sectorAddress, out buffer)) return ErrorNumber.NoError;
- long imageOff = _dataOff + (long)(BlockOffset(sectorAddress) * (_pHdr.blockSize + CRC_SIZE));
+ long imageOff = _dataOff + (long)(BlockOffset(sectorAddress) * (_pHdr.blockSize + (uint)_crcSize));
buffer = new byte[_pHdr.blockSize];
_imageStream.Seek(imageOff, SeekOrigin.Begin);
diff --git a/Aaru.Images/PartClone/Verify.cs b/Aaru.Images/PartClone/Verify.cs
index e713109ab..9b9def78a 100644
--- a/Aaru.Images/PartClone/Verify.cs
+++ b/Aaru.Images/PartClone/Verify.cs
@@ -30,15 +30,102 @@
// Copyright © 2011-2026 Natalia Portillo
// ****************************************************************************/
+using System;
+using System.IO;
+using Aaru.Helpers;
+using Aaru.Logging;
+
namespace Aaru.Images;
public sealed partial class PartClone
{
#region IVerifiableImage Members
- // TODO: All blocks contain a CRC32 that's incompatible with current implementation. Need to check for compatibility.
///
- public bool? VerifyMediaImage() => null;
+ ///
+ /// Verifies a partclone v0001 image by replaying the cumulative CRC-32 stored after each used block. The
+ /// checksum is produced by partclone's buggy crc32_0001() routine (see )
+ /// and is never reseeded, so the running seed is propagated from one block to the next.
+ ///
+ public bool? VerifyMediaImage()
+ {
+ if(_imageStream is null) return null;
+
+ if(_pHdr.usedBlocks == 0) return true;
+
+ var blockSize = (int)_pHdr.blockSize;
+
+ if(blockSize <= 0 || _crcSize != CRC_SIZE_NORMAL && _crcSize != CRC_SIZE_X64_BUG) return null;
+
+ var dataBuffer = new byte[blockSize];
+ var crcBuffer = new byte[_crcSize];
+ var allOk = true;
+
+ try
+ {
+ _imageStream.Seek(_dataOff, SeekOrigin.Begin);
+
+ uint running = CRC32_SEED;
+
+ for(ulong blockIdx = 0; blockIdx < _pHdr.usedBlocks; blockIdx++)
+ {
+ int dataRead = _imageStream.EnsureRead(dataBuffer, 0, blockSize);
+
+ if(dataRead != blockSize)
+ {
+ AaruLogging.Error(MODULE_NAME,
+ "Short read while verifying partclone data block {0}: expected {1}, got {2}",
+ blockIdx,
+ blockSize,
+ dataRead);
+
+ return null;
+ }
+
+ running = UpdateCrc32_0001(running, dataBuffer, blockSize);
+
+ int crcRead = _imageStream.EnsureRead(crcBuffer, 0, _crcSize);
+
+ if(crcRead != _crcSize)
+ {
+ AaruLogging.Error(MODULE_NAME,
+ "Short read while reading CRC trailer at data block {0}: expected {1}, got {2}",
+ blockIdx,
+ _crcSize,
+ crcRead);
+
+ return null;
+ }
+
+ // The x64 bug stores the CRC as an 8-byte little-endian value whose lower 4 bytes are the actual
+ // CRC (source cast the seed via `*(uint32_t*)checksum`). Upper 4 bytes are padding and ignored.
+ var storedCrc = BitConverter.ToUInt32(crcBuffer, 0);
+
+ if(storedCrc != running)
+ {
+ AaruLogging.Error(MODULE_NAME,
+ "CRC mismatch at data block {0}: stored 0x{1:X8}, computed 0x{2:X8}",
+ blockIdx,
+ storedCrc,
+ running);
+
+ allOk = false;
+ }
+ else
+ {
+ AaruLogging.Debug(MODULE_NAME, "Data block {0} CRC 0x{1:X8} OK", blockIdx, storedCrc);
+ }
+ }
+ }
+ catch(IOException ex)
+ {
+ AaruLogging.Error(MODULE_NAME, "I/O error while verifying partclone data blocks: {0}", ex.Message);
+
+ return null;
+ }
+
+ return allOk;
+ }
#endregion
}
\ No newline at end of file