From cb5faaa674a9984e71087cbf2e80a1493a930be6 Mon Sep 17 00:00:00 2001 From: Natalia Portillo Date: Tue, 21 Apr 2026 00:03:27 +0100 Subject: [PATCH] [NDIF] Add CRC verification. Fixes #88 --- .../Localization/Localization.Designer.cs | 12 ++ Aaru.Images/Localization/Localization.es.resx | 6 + Aaru.Images/Localization/Localization.resx | 6 + Aaru.Images/NDIF/NDIF.cs | 6 +- Aaru.Images/NDIF/NdifCrc28.cs | 104 +++++++++++++++++ Aaru.Images/NDIF/Verify.cs | 105 ++++++++++++++++++ 6 files changed, 235 insertions(+), 4 deletions(-) create mode 100644 Aaru.Images/NDIF/NdifCrc28.cs create mode 100644 Aaru.Images/NDIF/Verify.cs diff --git a/Aaru.Images/Localization/Localization.Designer.cs b/Aaru.Images/Localization/Localization.Designer.cs index cceab5663..254fad829 100644 --- a/Aaru.Images/Localization/Localization.Designer.cs +++ b/Aaru.Images/Localization/Localization.Designer.cs @@ -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); + } + } } } diff --git a/Aaru.Images/Localization/Localization.es.resx b/Aaru.Images/Localization/Localization.es.resx index 6de37284d..868a4352f 100644 --- a/Aaru.Images/Localization/Localization.es.resx +++ b/Aaru.Images/Localization/Localization.es.resx @@ -3060,4 +3060,10 @@ El bloque NRG incrustado en la imagen UIF de MagicISO no contiene pistas. + + [slateblue1]Verificando el CRC de la imagen NDIF…[/] + + + CRC de la imagen NDIF no coincide: se esperaba 0x{0:X7}, se obtuvo 0x{1:X7} + \ No newline at end of file diff --git a/Aaru.Images/Localization/Localization.resx b/Aaru.Images/Localization/Localization.resx index 69b7f6491..0a8960666 100644 --- a/Aaru.Images/Localization/Localization.resx +++ b/Aaru.Images/Localization/Localization.resx @@ -3079,4 +3079,10 @@ The NRG trailer embedded in the MagicISO UIF image has no tracks. + + [slateblue1]Verifying NDIF image CRC…[/] + + + NDIF image CRC mismatch: expected 0x{0:X7}, got 0x{1:X7} + \ No newline at end of file diff --git a/Aaru.Images/NDIF/NDIF.cs b/Aaru.Images/NDIF/NDIF.cs index bc25863d9..f87192348 100644 --- a/Aaru.Images/NDIF/NDIF.cs +++ b/Aaru.Images/NDIF/NDIF.cs @@ -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 -/// +/// /// Implements reading Apple New Disk Image Format disk images -public sealed partial class Ndif : IMediaImage +public sealed partial class Ndif : IMediaImage, IVerifiableImage { const string MODULE_NAME = "NDIF plugin"; uint _bufferSize; diff --git a/Aaru.Images/NDIF/NdifCrc28.cs b/Aaru.Images/NDIF/NdifCrc28.cs new file mode 100644 index 000000000..beb1a4eaa --- /dev/null +++ b/Aaru.Images/NDIF/NdifCrc28.cs @@ -0,0 +1,104 @@ +// /*************************************************************************** +// Aaru Data Preservation Suite +// ---------------------------------------------------------------------------- +// +// Filename : NdifCrc28.cs +// Author(s) : Natalia Portillo +// +// 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] & 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) & 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 . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2026 Natalia Portillo +// ****************************************************************************/ + +namespace Aaru.Images; + +/// Table-driven implementation of Apple NDIF's 28-bit CRC. +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; + } + + /// Returns the initial CRC accumulator seed. + public static uint Init() => SEED; + + /// + /// Feeds bytes from into the running + /// . + /// + 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; + } + + /// Applies the final 28-bit mask to , yielding the value stored in the NDIF header. + public static uint Finish(uint crc) => crc & MASK_28; +} \ No newline at end of file diff --git a/Aaru.Images/NDIF/Verify.cs b/Aaru.Images/NDIF/Verify.cs new file mode 100644 index 000000000..4169329a3 --- /dev/null +++ b/Aaru.Images/NDIF/Verify.cs @@ -0,0 +1,105 @@ +// /*************************************************************************** +// Aaru Data Preservation Suite +// ---------------------------------------------------------------------------- +// +// Filename : Verify.cs +// Author(s) : Natalia Portillo +// +// 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 . +// +// ---------------------------------------------------------------------------- +// 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 + + /// + /// Verifies the image against the 28-bit CRC stored in the NDIF bcem header. The CRC is computed over the + /// reconstructed 512-byte-per-sector stream in image order; CHUNK_TYPE_NOCOPY sectors feed in as zeros, + /// matching Apple DiskCopy's behaviour. + /// + /// + /// true if the recomputed CRC matches the one stored in the header; false otherwise; null when + /// there are no chunks to verify. + /// + 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 +} \ No newline at end of file