[NDIF] Add CRC verification. Fixes #88

This commit is contained in:
2026-04-21 00:03:27 +01:00
parent 3e123d5aa5
commit cb5faaa674
6 changed files with 235 additions and 4 deletions

View File

@@ -6135,5 +6135,17 @@ namespace Aaru.Images {
return ResourceManager.GetString("MagicIso_NRG_trailer_has_no_tracks", resourceCulture);
}
}
internal static string Verifying_NDIF_image_CRC {
get {
return ResourceManager.GetString("Verifying_NDIF_image_CRC", resourceCulture);
}
}
internal static string NDIF_CRC_mismatch_expected_0_got_1 {
get {
return ResourceManager.GetString("NDIF_CRC_mismatch_expected_0_got_1", resourceCulture);
}
}
}
}

View File

@@ -3060,4 +3060,10 @@
<data name="MagicIso_NRG_trailer_has_no_tracks" xml:space="preserve">
<value>El bloque NRG incrustado en la imagen UIF de MagicISO no contiene pistas.</value>
</data>
<data name="Verifying_NDIF_image_CRC" xml:space="preserve">
<value>[slateblue1]Verificando el CRC de la imagen NDIF…[/]</value>
</data>
<data name="NDIF_CRC_mismatch_expected_0_got_1" xml:space="preserve">
<value>CRC de la imagen NDIF no coincide: se esperaba 0x{0:X7}, se obtuvo 0x{1:X7}</value>
</data>
</root>

View File

@@ -3079,4 +3079,10 @@
<data name="MagicIso_NRG_trailer_has_no_tracks" xml:space="preserve">
<value>The NRG trailer embedded in the MagicISO UIF image has no tracks.</value>
</data>
<data name="Verifying_NDIF_image_CRC" xml:space="preserve">
<value>[slateblue1]Verifying NDIF image CRC…[/]</value>
</data>
<data name="NDIF_CRC_mismatch_expected_0_got_1" xml:space="preserve">
<value>NDIF image CRC mismatch: expected 0x{0:X7}, got 0x{1:X7}</value>
</data>
</root>

View File

@@ -38,12 +38,10 @@ using Aaru.CommonTypes.Structs;
namespace Aaru.Images;
// TODO: Detect OS X encrypted images
// TODO: Check checksum
// TODO: Implement segments
// TODO: Implement compression
/// <inheritdoc />
/// <inheritdoc cref="IMediaImage" />
/// <summary>Implements reading Apple New Disk Image Format disk images</summary>
public sealed partial class Ndif : IMediaImage
public sealed partial class Ndif : IMediaImage, IVerifiableImage
{
const string MODULE_NAME = "NDIF plugin";
uint _bufferSize;

View File

@@ -0,0 +1,104 @@
// /***************************************************************************
// Aaru Data Preservation Suite
// ----------------------------------------------------------------------------
//
// Filename : NdifCrc28.cs
// Author(s) : Natalia Portillo <claunia@claunia.com>
//
// Component : Disk image plugins.
//
// --[ Description ] ----------------------------------------------------------
//
// Implements the 28-bit CRC used to protect Apple New Disk Image Format
// images.
//
// Reverse-engineered from the 68k code fragment (`oneb` 128) embedded in
// the Apple DiskCopy 6.3.3 resource fork (exports `.CRC28_Initialize`,
// `.CRC28_Start`, `.CRC28_ProcessBuffer`, `.CRC28_Finish`):
//
// table[i] = i
// for each of 8 bits: if (table[i] &amp; 1)
// table[i] = (table[i] >> 1) ^ 0x04C11DB7
// else
// table[i] >>= 1
//
// crc = 0xFFFFFFFF
// for each byte b of the decoded sector stream:
// crc = (crc >> 8) ^ table[(crc ^ b) &amp; 0xFF]
//
// CRC28_Finish is a no-op; the low 28 bits of the accumulator are what the
// NDIF `bcem` header stores in its `crc` field.
//
// Buffer contents are the reconstructed 512-byte sectors of the image, in
// image order; `CHUNK_TYPE_NOCOPY` (and otherwise unwritten) sectors feed
// in as zeros.
//
// --[ License ] --------------------------------------------------------------
//
// This library is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation; either version 2.1 of the
// License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, see <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------
// Copyright © 2011-2026 Natalia Portillo
// ****************************************************************************/
namespace Aaru.Images;
/// <summary>Table-driven implementation of Apple NDIF's 28-bit CRC.</summary>
static class NdifCrc28
{
const uint POLYNOMIAL = 0x04C11DB7;
const uint SEED = 0xFFFFFFFF;
const uint MASK_28 = 0x0FFFFFFF;
static readonly uint[] _table = BuildTable();
static uint[] BuildTable()
{
var table = new uint[256];
for(uint i = 0; i < 256; i++)
{
uint c = i;
for(var j = 0; j < 8; j++)
{
if((c & 1) != 0)
c = c >> 1 ^ POLYNOMIAL;
else
c >>= 1;
}
table[i] = c;
}
return table;
}
/// <summary>Returns the initial CRC accumulator seed.</summary>
public static uint Init() => SEED;
/// <summary>
/// Feeds <paramref name="length" /> bytes from <paramref name="buffer" /> into the running
/// <paramref name="crc" />.
/// </summary>
public static uint Update(uint crc, byte[] buffer, int length)
{
for(var i = 0; i < length; i++) crc = crc >> 8 ^ _table[(crc ^ buffer[i]) & 0xFF];
return crc;
}
/// <summary>Applies the final 28-bit mask to <paramref name="crc" />, yielding the value stored in the NDIF header.</summary>
public static uint Finish(uint crc) => crc & MASK_28;
}

105
Aaru.Images/NDIF/Verify.cs Normal file
View File

@@ -0,0 +1,105 @@
// /***************************************************************************
// Aaru Data Preservation Suite
// ----------------------------------------------------------------------------
//
// Filename : Verify.cs
// Author(s) : Natalia Portillo <claunia@claunia.com>
//
// Component : Disk image plugins.
//
// --[ Description ] ----------------------------------------------------------
//
// Verifies the internal 28-bit CRC of an Apple New Disk Image Format
// image.
//
// --[ License ] --------------------------------------------------------------
//
// This library is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation; either version 2.1 of the
// License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, see <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------
// Copyright © 2011-2026 Natalia Portillo
// ****************************************************************************/
using System.Linq;
using Aaru.CommonTypes.Enums;
using Aaru.Logging;
namespace Aaru.Images;
public sealed partial class Ndif
{
#region IVerifiableImage Members
/// <summary>
/// Verifies the image against the 28-bit CRC stored in the NDIF <c>bcem</c> header. The CRC is computed over the
/// reconstructed 512-byte-per-sector stream in image order; <c>CHUNK_TYPE_NOCOPY</c> sectors feed in as zeros,
/// matching Apple DiskCopy's behaviour.
/// </summary>
/// <returns>
/// <c>true</c> if the recomputed CRC matches the one stored in the header; <c>false</c> otherwise; <c>null</c> when
/// there are no chunks to verify.
/// </returns>
public bool? VerifyMediaImage()
{
if(_chunks is null || _chunks.Count == 0) return null;
AaruLogging.WriteLine(Localization.Verifying_NDIF_image_CRC);
var zeroSector = new byte[SECTOR_SIZE];
uint crc = NdifCrc28.Init();
// NDIF BlockChunk does not carry an explicit sector count, so we derive it from the start of the next chunk (or
// the image total for the last one) after sorting by starting sector.
var ordered = _chunks.OrderBy(static kvp => kvp.Key).ToList();
for(var i = 0; i < ordered.Count; i++)
{
ulong start = ordered[i].Key;
BlockChunk chunk = ordered[i].Value;
if(chunk.type == CHUNK_TYPE_END) continue;
ulong end = i + 1 < ordered.Count ? ordered[i + 1].Key : _imageInfo.Sectors;
ulong chunkSectorCt = end - start;
if(chunk.type == CHUNK_TYPE_NOCOPY)
{
// Fast path: avoid going through ReadSector for empty regions.
for(ulong s = 0; s < chunkSectorCt; s++) crc = NdifCrc28.Update(crc, zeroSector, (int)SECTOR_SIZE);
continue;
}
for(ulong s = 0; s < chunkSectorCt; s++)
{
ErrorNumber errno = ReadSector(start + s, false, out byte[] sector, out _);
if(errno != ErrorNumber.NoError || sector is null) return false;
crc = NdifCrc28.Update(crc, sector, (int)SECTOR_SIZE);
}
}
uint calculated = NdifCrc28.Finish(crc);
uint stored = _header.crc & 0x0FFFFFFF;
if(calculated == stored) return true;
AaruLogging.WriteLine(Localization.NDIF_CRC_mismatch_expected_0_got_1, stored, calculated);
return false;
}
#endregion
}