// /*************************************************************************** // Aaru Data Preservation Suite // ---------------------------------------------------------------------------- // // Filename : ClauniaSubchannelTransform.cs // Author(s) : Natalia Portillo // // Component : Disk image plugins. // // --[ Description ] ---------------------------------------------------------- // // Contains the CD ECC algorithm. // // --[ 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-2023 Natalia Portillo // ECC algorithm from ECM(c) 2002-2011 Neill Corlett // ****************************************************************************/ using System; using Aaru.CommonTypes.Enums; namespace Aaru.Images; public sealed partial class AaruFormat { byte[] _eccBTable; byte[] _eccFTable; uint[] _edcTable; bool _initedEdc; void EccInit() { if(_initedEdc) return; _eccFTable = new byte[256]; _eccBTable = new byte[256]; _edcTable = new uint[256]; for(uint i = 0; i < 256; i++) { uint edc = i; var j = (uint)(i << 1 ^ ((i & 0x80) == 0x80 ? 0x11D : 0)); _eccFTable[i] = (byte)j; _eccBTable[i ^ j] = (byte)i; for(j = 0; j < 8; j++) edc = edc >> 1 ^ ((edc & 1) > 0 ? 0xD8018001 : 0); _edcTable[i] = edc; } _initedEdc = true; } bool SuffixIsCorrect(byte[] sector) { if(!_initedEdc) EccInit(); if(sector[0x814] != 0x00 || // reserved (8 bytes) sector[0x815] != 0x00 || sector[0x816] != 0x00 || sector[0x817] != 0x00 || sector[0x818] != 0x00 || sector[0x819] != 0x00 || sector[0x81A] != 0x00 || sector[0x81B] != 0x00) return false; bool correctEccP = CheckEcc(sector, sector, 86, 24, 2, 86, sector, 0xC, 0x10, 0x81C); if(!correctEccP) return false; bool correctEccQ = CheckEcc(sector, sector, 52, 43, 86, 88, sector, 0xC, 0x10, 0x81C + 0xAC); if(!correctEccQ) return false; var storedEdc = BitConverter.ToUInt32(sector, 0x810); uint edc = 0; var size = 0x810; var pos = 0; for(; size > 0; size--) edc = edc >> 8 ^ _edcTable[(edc ^ sector[pos++]) & 0xFF]; uint calculatedEdc = edc; return calculatedEdc == storedEdc; } bool SuffixIsCorrectMode2(byte[] sector) { if(!_initedEdc) EccInit(); var zeroAddress = new byte[4]; bool correctEccP = CheckEcc(zeroAddress, sector, 86, 24, 2, 86, sector, 0, 0x10, 0x81C); if(!correctEccP) return false; bool correctEccQ = CheckEcc(zeroAddress, sector, 52, 43, 86, 88, sector, 0, 0x10, 0x81C + 0xAC); if(!correctEccQ) return false; var storedEdc = BitConverter.ToUInt32(sector, 0x818); uint edc = 0; var size = 0x808; var pos = 0x10; for(; size > 0; size--) edc = edc >> 8 ^ _edcTable[(edc ^ sector[pos++]) & 0xFF]; uint calculatedEdc = edc; return calculatedEdc == storedEdc; } bool CheckEcc(byte[] address, byte[] data, uint majorCount, uint minorCount, uint majorMult, uint minorInc, byte[] ecc, int addressOffset, int dataOffset, int eccOffset) { uint size = majorCount * minorCount; uint major; for(major = 0; major < majorCount; major++) { uint idx = (major >> 1) * majorMult + (major & 1); byte eccA = 0; byte eccB = 0; uint minor; for(minor = 0; minor < minorCount; minor++) { byte temp = idx < 4 ? address[idx + addressOffset] : data[idx + dataOffset - 4]; idx += minorInc; if(idx >= size) idx -= size; eccA ^= temp; eccB ^= temp; eccA = _eccFTable[eccA]; } eccA = _eccBTable[_eccFTable[eccA] ^ eccB]; if(ecc[major + eccOffset] != eccA || ecc[major + majorCount + eccOffset] != (eccA ^ eccB)) return false; } return true; } void WriteEcc(byte[] address, byte[] data, uint majorCount, uint minorCount, uint majorMult, uint minorInc, ref byte[] ecc, int addressOffset, int dataOffset, int eccOffset) { uint size = majorCount * minorCount; uint major; for(major = 0; major < majorCount; major++) { uint idx = (major >> 1) * majorMult + (major & 1); byte eccA = 0; byte eccB = 0; uint minor; for(minor = 0; minor < minorCount; minor++) { byte temp = idx < 4 ? address[idx + addressOffset] : data[idx + dataOffset - 4]; idx += minorInc; if(idx >= size) idx -= size; eccA ^= temp; eccB ^= temp; eccA = _eccFTable[eccA]; } eccA = _eccBTable[_eccFTable[eccA] ^ eccB]; ecc[major + eccOffset] = eccA; ecc[major + majorCount + eccOffset] = (byte)(eccA ^ eccB); } } void EccWriteSector(byte[] address, byte[] data, ref byte[] ecc, int addressOffset, int dataOffset, int eccOffset) { WriteEcc(address, data, 86, 24, 2, 86, ref ecc, addressOffset, dataOffset, eccOffset); // P WriteEcc(address, data, 52, 43, 86, 88, ref ecc, addressOffset, dataOffset, eccOffset + 0xAC); // Q } static (byte minute, byte second, byte frame) LbaToMsf(long pos) => ((byte)((pos + 150) / 75 / 60), (byte)((pos + 150) / 75 % 60), (byte)((pos + 150) % 75)); static void ReconstructPrefix(ref byte[] sector, // must point to a full 2352-byte sector TrackType type, long lba) { // // Sync // sector[0x000] = 0x00; sector[0x001] = 0xFF; sector[0x002] = 0xFF; sector[0x003] = 0xFF; sector[0x004] = 0xFF; sector[0x005] = 0xFF; sector[0x006] = 0xFF; sector[0x007] = 0xFF; sector[0x008] = 0xFF; sector[0x009] = 0xFF; sector[0x00A] = 0xFF; sector[0x00B] = 0x00; (byte minute, byte second, byte frame) msf = LbaToMsf(lba); sector[0x00C] = (byte)((msf.minute / 10 << 4) + msf.minute % 10); sector[0x00D] = (byte)((msf.second / 10 << 4) + msf.second % 10); sector[0x00E] = (byte)((msf.frame / 10 << 4) + msf.frame % 10); switch(type) { case TrackType.CdMode1: // // Mode // sector[0x00F] = 0x01; break; case TrackType.CdMode2Form1: case TrackType.CdMode2Form2: case TrackType.CdMode2Formless: // // Mode // sector[0x00F] = 0x02; // // Flags // sector[0x010] = sector[0x014]; sector[0x011] = sector[0x015]; sector[0x012] = sector[0x016]; sector[0x013] = sector[0x017]; break; default: return; } } void ReconstructEcc(ref byte[] sector, // must point to a full 2352-byte sector TrackType type) { byte[] computedEdc; if(!_initedEdc) EccInit(); switch(type) { // // Compute EDC // case TrackType.CdMode1: computedEdc = BitConverter.GetBytes(ComputeEdc(0, sector, 0x810)); sector[0x810] = computedEdc[0]; sector[0x811] = computedEdc[1]; sector[0x812] = computedEdc[2]; sector[0x813] = computedEdc[3]; break; case TrackType.CdMode2Form1: computedEdc = BitConverter.GetBytes(ComputeEdc(0, sector, 0x808, 0x10)); sector[0x818] = computedEdc[0]; sector[0x819] = computedEdc[1]; sector[0x81A] = computedEdc[2]; sector[0x81B] = computedEdc[3]; break; case TrackType.CdMode2Form2: computedEdc = BitConverter.GetBytes(ComputeEdc(0, sector, 0x91C, 0x10)); sector[0x92C] = computedEdc[0]; sector[0x92D] = computedEdc[1]; sector[0x92E] = computedEdc[2]; sector[0x92F] = computedEdc[3]; break; default: return; } var zeroAddress = new byte[4]; switch(type) { // // Compute ECC // case TrackType.CdMode1: // // Reserved // sector[0x814] = 0x00; sector[0x815] = 0x00; sector[0x816] = 0x00; sector[0x817] = 0x00; sector[0x818] = 0x00; sector[0x819] = 0x00; sector[0x81A] = 0x00; sector[0x81B] = 0x00; EccWriteSector(sector, sector, ref sector, 0xC, 0x10, 0x81C); break; case TrackType.CdMode2Form1: EccWriteSector(zeroAddress, sector, ref sector, 0, 0x10, 0x81C); break; default: return; } // // Done // } uint ComputeEdc(uint edc, byte[] src, int size, int srcOffset = 0) { if(!_initedEdc) EccInit(); int pos = srcOffset; for(; size > 0; size--) edc = edc >> 8 ^ _edcTable[(edc ^ src[pos++]) & 0xFF]; return edc; } }