// /*************************************************************************** // Aaru Data Preservation Suite // ---------------------------------------------------------------------------- // // Filename : CompactDisc.cs // Author(s) : Natalia Portillo // // Component : Core algorithms. // // --[ License ] -------------------------------------------------------------- // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as // published by the Free Software Foundation, either version 3 of the // License, or (at your option) any later version. // // This program 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 General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . // // ---------------------------------------------------------------------------- // Copyright © 2011-2025 Natalia Portillo // ****************************************************************************/ using System; using System.Collections.Generic; using System.Linq; using Aaru.Checksums; using Aaru.CommonTypes; using Aaru.CommonTypes.Enums; using Aaru.CommonTypes.Interfaces; using Aaru.CommonTypes.Structs; using Aaru.Core.Logging; using Aaru.Decoders.CD; using Aaru.Devices; using Aaru.Helpers; using Aaru.Logging; namespace Aaru.Core.Media; /// Operations over CD based media public static class CompactDisc { /// Writes subchannel data to an image /// Subchannel read by drive /// Subchannel user wants written to image /// Subchannel data /// Starting sector /// How many sectors were read /// Subchannel log /// List of ISRCs /// Current track number /// Disc's MCN /// List of tracks /// List of subchannel extents /// If we want to fix subchannel position /// Output image /// If we want to fix subchannel contents /// If we want to fix Q subchannel CRC if the contents look sane /// Status update callback /// List of smallest known pregap per track /// Set if we are dumping, otherwise converting /// /// Gets a list of the new sectors that should be considered part of a pregap, and then /// re-read /// /// true if indexes have changed, false otherwise public static bool WriteSubchannelToImage(MmcSubchannel supportedSubchannel, MmcSubchannel desiredSubchannel, byte[] sub, ulong sectorAddress, uint length, SubchannelLog subLog, Dictionary isrcs, byte currentTrack, ref string mcn, Track[] tracks, HashSet subchannelExtents, bool fixSubchannelPosition, IWritableOpticalImage outputPlugin, bool fixSubchannel, bool fixSubchannelCrc, UpdateStatusHandler updateStatus, Dictionary smallestPregapLbaPerTrack, bool dumping, out List newPregapSectors) { // We need to work in PW raw subchannels if(supportedSubchannel == MmcSubchannel.Q16) sub = Subchannel.ConvertQToRaw(sub); // If not desired to fix, or to save, the subchannel, just save as is (or none) if(!fixSubchannelPosition && desiredSubchannel != MmcSubchannel.None) outputPlugin.WriteSectorsTag(sub, sectorAddress, false, length, SectorTagType.CdSectorSubchannel); subLog?.WriteEntry(sub, supportedSubchannel == MmcSubchannel.Raw, (long)sectorAddress, length, false, false); byte[] deSub = Subchannel.Deinterleave(sub); bool indexesChanged = CheckIndexesFromSubchannel(deSub, isrcs, currentTrack, ref mcn, tracks, updateStatus, smallestPregapLbaPerTrack, dumping, out newPregapSectors, sectorAddress); if(!fixSubchannelPosition || desiredSubchannel == MmcSubchannel.None) return indexesChanged; int prePos = int.MinValue; // Check subchannel for(var subPos = 0; subPos < deSub.Length; subPos += 96) { // Expected LBA long lba = (long)sectorAddress + subPos / 96; // We fixed the subchannel var @fixed = false; var q = new byte[12]; Array.Copy(deSub, subPos + 12, q, 0, 12); // Check Q CRC CRC16CcittContext.Data(q, 10, out byte[] crc); bool crcOk = crc[0] == q[10] && crc[1] == q[11]; // Start considering P to be OK var pOk = true; var pWeight = 0; // Check P and weight for(int p = subPos; p < subPos + 12; p++) { if(deSub[p] != 0 && deSub[p] != 255) pOk = false; for(var w = 0; w < 8; w++) if((deSub[p] >> w & 1) > 0) pWeight++; } // This seems to be a somewhat common pattern bool rOk = deSub.Skip(subPos + 24).Take(12).All(static r => r == 0x00) || deSub.Skip(subPos + 24).Take(12).All(static r => r == 0xFF); bool sOk = deSub.Skip(subPos + 36).Take(12).All(static s => s == 0x00) || deSub.Skip(subPos + 36).Take(12).All(static s => s == 0xFF); bool tOk = deSub.Skip(subPos + 48).Take(12).All(static t => t == 0x00) || deSub.Skip(subPos + 48).Take(12).All(static t => t == 0xFF); bool uOk = deSub.Skip(subPos + 60).Take(12).All(static u => u == 0x00) || deSub.Skip(subPos + 60).Take(12).All(static u => u == 0xFF); bool vOk = deSub.Skip(subPos + 72).Take(12).All(static v => v == 0x00) || deSub.Skip(subPos + 72).Take(12).All(static v => v == 0xFF); bool wOk = deSub.Skip(subPos + 84).Take(12).All(static w => w == 0x00) || deSub.Skip(subPos + 84).Take(12).All(static w => w == 0xFF); bool rwOk = rOk && sOk && tOk && uOk && vOk && wOk; var rwPacket = false; var cdtextPacket = false; // Check RW contents if(!rwOk) { var sectorSub = new byte[96]; Array.Copy(sub, subPos, sectorSub, 0, 96); DetectRwPackets(sectorSub, out _, out rwPacket, out cdtextPacket); // TODO: CD+G reed solomon if(rwPacket && !cdtextPacket) rwOk = true; if(cdtextPacket) rwOk = CheckCdTextPackets(sectorSub); } // Fix P if(!pOk && fixSubchannel) { if(pWeight >= 48) for(int p = subPos; p < subPos + 12; p++) deSub[p] = 255; else for(int p = subPos; p < subPos + 12; p++) deSub[p] = 0; pOk = true; @fixed = true; subLog?.WritePFix(lba); } // RW is not a known pattern or packet, fix it if(!rwOk && !rwPacket && !cdtextPacket && fixSubchannel) { for(int rw = subPos + 24; rw < subPos + 96; rw++) deSub[rw] = 0; rwOk = true; @fixed = true; subLog?.WriteRwFix(lba); } int aPos; // Fix Q if(!crcOk && fixSubchannel && subPos > 0 && subPos < deSub.Length - 96) { isrcs.TryGetValue(currentTrack, out string knownGoodIsrc); crcOk = FixQSubchannel(deSub, q, subPos, mcn, knownGoodIsrc, fixSubchannelCrc, out bool fixedAdr, out bool controlFix, out bool fixedZero, out bool fixedTno, out bool fixedIndex, out bool fixedRelPos, out bool fixedAbsPos, out bool fixedCrc, out bool fixedMcn, out bool fixedIsrc); if(crcOk) { Array.Copy(q, 0, deSub, subPos + 12, 12); @fixed = true; if(fixedAdr) subLog?.WriteQAdrFix(lba); if(controlFix) subLog?.WriteQCtrlFix(lba); if(fixedZero) subLog?.WriteQZeroFix(lba); if(fixedTno) subLog?.WriteQTnoFix(lba); if(fixedIndex) subLog?.WriteQIndexFix(lba); if(fixedRelPos) subLog?.WriteQRelPosFix(lba); if(fixedAbsPos) subLog?.WriteQAbsPosFix(lba); if(fixedCrc) subLog?.WriteQCrcFix(lba); if(fixedMcn) subLog?.WriteQMcnFix(lba); if(fixedIsrc) subLog?.WriteQIsrcFix(lba); } } if(!pOk || !crcOk || !rwOk) continue; var aframe = (byte)(q[9] / 16 * 10 + (q[9] & 0x0F)); if((q[0] & 0x3) == 1) { var amin = (byte)(q[7] / 16 * 10 + (q[7] & 0x0F)); var asec = (byte)(q[8] / 16 * 10 + (q[8] & 0x0F)); aPos = amin * 60 * 75 + asec * 75 + aframe - 150; } else { ulong expectedSectorAddress = sectorAddress + (ulong)(subPos / 96) + 150; var smin = (byte)(expectedSectorAddress / 60 / 75); expectedSectorAddress -= (ulong)(smin * 60 * 75); var ssec = (byte)(expectedSectorAddress / 75); aPos = smin * 60 * 75 + ssec * 75 + aframe - 150; // Next second if(aPos < prePos) aPos += 75; } // TODO: Negative sectors if(aPos < 0) continue; prePos = aPos; var posSub = new byte[96]; Array.Copy(deSub, subPos, posSub, 0, 96); posSub = Subchannel.Interleave(posSub); outputPlugin.WriteSectorTag(posSub, (ulong)aPos, false, SectorTagType.CdSectorSubchannel); subchannelExtents.Remove(aPos); if(@fixed) subLog?.WriteEntry(posSub, supportedSubchannel == MmcSubchannel.Raw, lba, 1, false, true); } return indexesChanged; } /// Check subchannel for indexes /// De-interleaved subchannel /// List of ISRCs /// Current track number /// Disc's MCN /// List of tracks /// Dumping log /// Status update callback /// List of smallest known pregap per track /// Set if we are dumping, otherwise converting /// /// Gets a list of the new sectors that should be considered part of a pregap, and then /// re-read /// /// Sector address the subchannel was read from /// true if indexes have changed, false otherwise static bool CheckIndexesFromSubchannel(byte[] deSub, Dictionary isrcs, byte currentTrackNumber, ref string mcn, Track[] tracks, UpdateStatusHandler updateStatus, Dictionary smallestPregapLbaPerTrack, bool dumping, out List newPregapSectors, ulong sectorAddress) { var status = false; newPregapSectors = []; // Check subchannel for(var subPos = 0; subPos < deSub.Length; subPos += 96) { var q = new byte[12]; Array.Copy(deSub, subPos + 12, q, 0, 12); CRC16CcittContext.Data(q, 10, out byte[] crc); bool crcOk = crc[0] == q[10] && crc[1] == q[11]; switch(q[0] & 0x3) { // ISRC case 3: { string isrc = Subchannel.DecodeIsrc(q); if(isrc is null or "000000000000") continue; if(!crcOk) continue; if(!isrcs.ContainsKey(currentTrackNumber)) { updateStatus?.Invoke(string.Format(Localization.Core.Found_new_ISRC_0_for_track_1, isrc, currentTrackNumber)); isrcs[currentTrackNumber] = isrc; } else if(isrcs[currentTrackNumber] != isrc) { Track currentTrack = tracks.FirstOrDefault(t => sectorAddress + (ulong)subPos / 96 >= t.StartSector); if(currentTrack?.Sequence == currentTrackNumber) { updateStatus?.Invoke(string.Format(Localization.Core.ISRC_for_track_0_changed_from_1_to_2, currentTrackNumber, isrcs[currentTrackNumber], isrc)); isrcs[currentTrackNumber] = isrc; } } break; } // MCN case 2: { string newMcn = Subchannel.DecodeMcn(q); if(newMcn is null or "0000000000000") continue; if(!crcOk) continue; if(mcn is null) updateStatus?.Invoke(string.Format(Localization.Core.Found_new_MCN_0, newMcn)); else if(mcn != newMcn) updateStatus?.Invoke(string.Format(Localization.Core.MCN_changed_from_0_to_1, mcn, newMcn)); mcn = newMcn; break; } // Positioning case 1 when !crcOk: continue; case 1: { var trackNo = (byte)(q[1] / 16 * 10 + (q[1] & 0x0F)); for(var i = 0; i < tracks.Length; i++) { if(tracks[i].Sequence != trackNo) continue; // Pregap if(q[2] == 0 && trackNo > 1) { var pmin = (byte)(q[3] / 16 * 10 + (q[3] & 0x0F)); var psec = (byte)(q[4] / 16 * 10 + (q[4] & 0x0F)); var pframe = (byte)(q[5] / 16 * 10 + (q[5] & 0x0F)); int qPos = pmin * 60 * 75 + psec * 75 + pframe; // When we are dumping we calculate the pregap in reverse from index 1 back. // When we are not, we go from index 0. smallestPregapLbaPerTrack.TryAdd(trackNo, dumping ? 1 : 0); uint firstTrackNumberInSameSession = tracks.Where(t => t.Session == tracks[i].Session) .Min(static t => t.Sequence); if(tracks[i].Sequence == firstTrackNumberInSameSession) continue; if(qPos < smallestPregapLbaPerTrack[trackNo]) { int dif = smallestPregapLbaPerTrack[trackNo] - qPos; tracks[i].Pregap += (ulong)dif; tracks[i].StartSector -= (ulong)dif; smallestPregapLbaPerTrack[trackNo] = qPos; if(i > 0 && tracks[i - 1].EndSector >= tracks[i].StartSector) tracks[i - 1].EndSector = tracks[i].StartSector - 1; updateStatus?.Invoke(string.Format(Localization.Core .Pregap_for_track_0_set_to_1_sectors, trackNo, tracks[i].Pregap)); for(var p = 0; p < dif; p++) newPregapSectors.Add(tracks[i].StartSector + (ulong)p); status = true; } if(tracks[i].Pregap >= (ulong)qPos) continue; ulong oldPregap = tracks[i].Pregap; tracks[i].Pregap = (ulong)qPos; tracks[i].StartSector -= tracks[i].Pregap - oldPregap; if(i > 0 && tracks[i - 1].EndSector >= tracks[i].StartSector) tracks[i - 1].EndSector = tracks[i].StartSector - 1; updateStatus?.Invoke(string.Format(Localization.Core.Pregap_for_track_0_set_to_1_sectors, trackNo, tracks[i].Pregap)); for(var p = 0; p < (int)(tracks[i].Pregap - oldPregap); p++) newPregapSectors.Add(tracks[i].StartSector + (ulong)p); status = true; continue; } if(q[2] == 0) continue; var amin = (byte)(q[7] / 16 * 10 + (q[7] & 0x0F)); var asec = (byte)(q[8] / 16 * 10 + (q[8] & 0x0F)); var aframe = (byte)(q[9] / 16 * 10 + (q[9] & 0x0F)); int aPos = amin * 60 * 75 + asec * 75 + aframe - 150; // Do not set INDEX 1 to a value higher than what the TOC already said. if(q[2] == 1 && aPos > (int)tracks[i].StartSector) continue; if(tracks[i].Indexes.ContainsKey(q[2]) && aPos >= tracks[i].Indexes[q[2]]) continue; updateStatus?.Invoke(string.Format(Localization.Core.Setting_index_0_for_track_1_to_LBA_2, q[2], trackNo, aPos)); tracks[i].Indexes[q[2]] = aPos; status = true; } break; } } } return status; } /// Detect RW packets /// Subchannel data /// Set if it contains a ZERO packet /// Set if it contains a GRAPHICS, EXTENDED GRAPHICS or MIDI packet /// Set if it contains a TEXT packet static void DetectRwPackets(byte[] subchannel, out bool zero, out bool rwPacket, out bool cdtextPacket) { zero = false; rwPacket = false; cdtextPacket = false; var cdTextPack1 = new byte[18]; var cdTextPack2 = new byte[18]; var cdTextPack3 = new byte[18]; var cdTextPack4 = new byte[18]; var cdSubRwPack1 = new byte[24]; var cdSubRwPack2 = new byte[24]; var cdSubRwPack3 = new byte[24]; var cdSubRwPack4 = new byte[24]; var i = 0; for(var j = 0; j < 18; j++) { cdTextPack1[j] = (byte)(cdTextPack1[j] | (subchannel[i++] & 0x3F) << 2); cdTextPack1[j] = (byte)(cdTextPack1[j++] | (subchannel[i] & 0xC0) >> 4); if(j < 18) cdTextPack1[j] = (byte)(cdTextPack1[j] | (subchannel[i++] & 0x0F) << 4); if(j < 18) cdTextPack1[j] = (byte)(cdTextPack1[j++] | (subchannel[i] & 0x3C) >> 2); if(j < 18) cdTextPack1[j] = (byte)(cdTextPack1[j] | (subchannel[i++] & 0x03) << 6); if(j < 18) cdTextPack1[j] = (byte)(cdTextPack1[j] | subchannel[i++] & 0x3F); } for(var j = 0; j < 18; j++) { cdTextPack2[j] = (byte)(cdTextPack2[j] | (subchannel[i++] & 0x3F) << 2); cdTextPack2[j] = (byte)(cdTextPack2[j++] | (subchannel[i] & 0xC0) >> 4); if(j < 18) cdTextPack2[j] = (byte)(cdTextPack2[j] | (subchannel[i++] & 0x0F) << 4); if(j < 18) cdTextPack2[j] = (byte)(cdTextPack2[j++] | (subchannel[i] & 0x3C) >> 2); if(j < 18) cdTextPack2[j] = (byte)(cdTextPack2[j] | (subchannel[i++] & 0x03) << 6); if(j < 18) cdTextPack2[j] = (byte)(cdTextPack2[j] | subchannel[i++] & 0x3F); } for(var j = 0; j < 18; j++) { cdTextPack3[j] = (byte)(cdTextPack3[j] | (subchannel[i++] & 0x3F) << 2); cdTextPack3[j] = (byte)(cdTextPack3[j++] | (subchannel[i] & 0xC0) >> 4); if(j < 18) cdTextPack3[j] = (byte)(cdTextPack3[j] | (subchannel[i++] & 0x0F) << 4); if(j < 18) cdTextPack3[j] = (byte)(cdTextPack3[j++] | (subchannel[i] & 0x3C) >> 2); if(j < 18) cdTextPack3[j] = (byte)(cdTextPack3[j] | (subchannel[i++] & 0x03) << 6); if(j < 18) cdTextPack3[j] = (byte)(cdTextPack3[j] | subchannel[i++] & 0x3F); } for(var j = 0; j < 18; j++) { cdTextPack4[j] = (byte)(cdTextPack4[j] | (subchannel[i++] & 0x3F) << 2); cdTextPack4[j] = (byte)(cdTextPack4[j++] | (subchannel[i] & 0xC0) >> 4); if(j < 18) cdTextPack4[j] = (byte)(cdTextPack4[j] | (subchannel[i++] & 0x0F) << 4); if(j < 18) cdTextPack4[j] = (byte)(cdTextPack4[j++] | (subchannel[i] & 0x3C) >> 2); if(j < 18) cdTextPack4[j] = (byte)(cdTextPack4[j] | (subchannel[i++] & 0x03) << 6); if(j < 18) cdTextPack4[j] = (byte)(cdTextPack4[j] | subchannel[i++] & 0x3F); } i = 0; for(var j = 0; j < 24; j++) cdSubRwPack1[j] = (byte)(subchannel[i++] & 0x3F); for(var j = 0; j < 24; j++) cdSubRwPack2[j] = (byte)(subchannel[i++] & 0x3F); for(var j = 0; j < 24; j++) cdSubRwPack3[j] = (byte)(subchannel[i++] & 0x3F); for(var j = 0; j < 24; j++) cdSubRwPack4[j] = (byte)(subchannel[i++] & 0x3F); switch(cdSubRwPack1[0]) { case 0x00: zero = true; break; case 0x08: case 0x09: case 0x0A: case 0x18: case 0x38: rwPacket = true; break; case 0x14: cdtextPacket = true; break; } switch(cdSubRwPack2[0]) { case 0x00: zero = true; break; case 0x08: case 0x09: case 0x0A: case 0x18: case 0x38: rwPacket = true; break; case 0x14: cdtextPacket = true; break; } switch(cdSubRwPack3[0]) { case 0x00: zero = true; break; case 0x08: case 0x09: case 0x0A: case 0x18: case 0x38: rwPacket = true; break; case 0x14: cdtextPacket = true; break; } switch(cdSubRwPack4[0]) { case 0x00: zero = true; break; case 0x08: case 0x09: case 0x0A: case 0x18: case 0x38: rwPacket = true; break; case 0x14: cdtextPacket = true; break; } if((cdTextPack1[0] & 0x80) == 0x80) cdtextPacket = true; if((cdTextPack2[0] & 0x80) == 0x80) cdtextPacket = true; if((cdTextPack3[0] & 0x80) == 0x80) cdtextPacket = true; if((cdTextPack4[0] & 0x80) == 0x80) cdtextPacket = true; } /// Checks if subchannel contains a TEXT packet /// Subchannel data /// true if subchannel contains a TEXT packet, false otherwise static bool CheckCdTextPackets(byte[] subchannel) { var cdTextPack1 = new byte[18]; var cdTextPack2 = new byte[18]; var cdTextPack3 = new byte[18]; var cdTextPack4 = new byte[18]; var i = 0; for(var j = 0; j < 18; j++) { cdTextPack1[j] = (byte)(cdTextPack1[j] | (subchannel[i++] & 0x3F) << 2); cdTextPack1[j] = (byte)(cdTextPack1[j++] | (subchannel[i] & 0xC0) >> 4); if(j < 18) cdTextPack1[j] = (byte)(cdTextPack1[j] | (subchannel[i++] & 0x0F) << 4); if(j < 18) cdTextPack1[j] = (byte)(cdTextPack1[j++] | (subchannel[i] & 0x3C) >> 2); if(j < 18) cdTextPack1[j] = (byte)(cdTextPack1[j] | (subchannel[i++] & 0x03) << 6); if(j < 18) cdTextPack1[j] = (byte)(cdTextPack1[j] | subchannel[i++] & 0x3F); } for(var j = 0; j < 18; j++) { cdTextPack2[j] = (byte)(cdTextPack2[j] | (subchannel[i++] & 0x3F) << 2); cdTextPack2[j] = (byte)(cdTextPack2[j++] | (subchannel[i] & 0xC0) >> 4); if(j < 18) cdTextPack2[j] = (byte)(cdTextPack2[j] | (subchannel[i++] & 0x0F) << 4); if(j < 18) cdTextPack2[j] = (byte)(cdTextPack2[j++] | (subchannel[i] & 0x3C) >> 2); if(j < 18) cdTextPack2[j] = (byte)(cdTextPack2[j] | (subchannel[i++] & 0x03) << 6); if(j < 18) cdTextPack2[j] = (byte)(cdTextPack2[j] | subchannel[i++] & 0x3F); } for(var j = 0; j < 18; j++) { cdTextPack3[j] = (byte)(cdTextPack3[j] | (subchannel[i++] & 0x3F) << 2); cdTextPack3[j] = (byte)(cdTextPack3[j++] | (subchannel[i] & 0xC0) >> 4); if(j < 18) cdTextPack3[j] = (byte)(cdTextPack3[j] | (subchannel[i++] & 0x0F) << 4); if(j < 18) cdTextPack3[j] = (byte)(cdTextPack3[j++] | (subchannel[i] & 0x3C) >> 2); if(j < 18) cdTextPack3[j] = (byte)(cdTextPack3[j] | (subchannel[i++] & 0x03) << 6); if(j < 18) cdTextPack3[j] = (byte)(cdTextPack3[j] | subchannel[i++] & 0x3F); } for(var j = 0; j < 18; j++) { cdTextPack4[j] = (byte)(cdTextPack4[j] | (subchannel[i++] & 0x3F) << 2); cdTextPack4[j] = (byte)(cdTextPack4[j++] | (subchannel[i] & 0xC0) >> 4); if(j < 18) cdTextPack4[j] = (byte)(cdTextPack4[j] | (subchannel[i++] & 0x0F) << 4); if(j < 18) cdTextPack4[j] = (byte)(cdTextPack4[j++] | (subchannel[i] & 0x3C) >> 2); if(j < 18) cdTextPack4[j] = (byte)(cdTextPack4[j] | (subchannel[i++] & 0x03) << 6); if(j < 18) cdTextPack4[j] = (byte)(cdTextPack4[j] | subchannel[i++] & 0x3F); } var status = true; if((cdTextPack1[0] & 0x80) == 0x80) { var cdTextPack1Crc = BigEndianBitConverter.ToUInt16(cdTextPack1, 16); var cdTextPack1ForCrc = new byte[16]; Array.Copy(cdTextPack1, 0, cdTextPack1ForCrc, 0, 16); ushort calculatedCdtp1Crc = CRC16CcittContext.Calculate(cdTextPack1ForCrc); if(cdTextPack1Crc != calculatedCdtp1Crc && cdTextPack1Crc != 0) status = false; } if((cdTextPack2[0] & 0x80) == 0x80) { var cdTextPack2Crc = BigEndianBitConverter.ToUInt16(cdTextPack2, 16); var cdTextPack2ForCrc = new byte[16]; Array.Copy(cdTextPack2, 0, cdTextPack2ForCrc, 0, 16); ushort calculatedCdtp2Crc = CRC16CcittContext.Calculate(cdTextPack2ForCrc); if(cdTextPack2Crc != calculatedCdtp2Crc && cdTextPack2Crc != 0) status = false; } if((cdTextPack3[0] & 0x80) == 0x80) { var cdTextPack3Crc = BigEndianBitConverter.ToUInt16(cdTextPack3, 16); var cdTextPack3ForCrc = new byte[16]; Array.Copy(cdTextPack3, 0, cdTextPack3ForCrc, 0, 16); ushort calculatedCdtp3Crc = CRC16CcittContext.Calculate(cdTextPack3ForCrc); if(cdTextPack3Crc != calculatedCdtp3Crc && cdTextPack3Crc != 0) status = false; } if((cdTextPack4[0] & 0x80) != 0x80) return status; var cdTextPack4Crc = BigEndianBitConverter.ToUInt16(cdTextPack4, 16); var cdTextPack4ForCrc = new byte[16]; Array.Copy(cdTextPack4, 0, cdTextPack4ForCrc, 0, 16); ushort calculatedCdtp4Crc = CRC16CcittContext.Calculate(cdTextPack4ForCrc); if(cdTextPack4Crc == calculatedCdtp4Crc || cdTextPack4Crc == 0) return status; return false; } /// Fixes Q subchannel /// Deinterleaved subchannel data /// Q subchannel /// Position in deSub /// Disc's MCN /// Track ISRC /// Set to true if we should fix the CRC, false otherwise /// Set to true if we fixed the ADR, false otherwise /// Set to true if we fixed the CONTROL, false otherwise /// Set to true if we fixed the ZERO, false otherwise /// Set to true if we fixed the TNO, false otherwise /// Set to true if we fixed the INDEX, false otherwise /// Set to true if we fixed the PMIN, PSEC and/or PFRAME, false otherwise /// Set to true if we fixed the AMIN, ASEC and/or AFRAME, false otherwise /// Set to true if we fixed the CRC, false otherwise /// Set to true if we fixed the MCN, false otherwise /// Set to true if we fixed the ISRC, false otherwise /// true if it was fixed correctly, false otherwise static bool FixQSubchannel(byte[] deSub, byte[] q, int subPos, string mcn, string isrc, bool fixCrc, out bool fixedAdr, out bool controlFix, out bool fixedZero, out bool fixedTno, out bool fixedIndex, out bool fixedRelPos, out bool fixedAbsPos, out bool fixedCrc, out bool fixedMcn, out bool fixedIsrc) { byte aframe; byte rframe; controlFix = false; fixedZero = false; fixedTno = false; fixedIndex = false; fixedRelPos = false; fixedAbsPos = false; fixedCrc = false; fixedMcn = false; fixedIsrc = false; var preQ = new byte[12]; var nextQ = new byte[12]; Array.Copy(deSub, subPos + 12 - 96, preQ, 0, 12); Array.Copy(deSub, subPos + 12 + 96, nextQ, 0, 12); CRC16CcittContext.Data(preQ, 10, out byte[] preCrc); bool preCrcOk = preCrc[0] == preQ[10] && preCrc[1] == preQ[11]; CRC16CcittContext.Data(nextQ, 10, out byte[] nextCrc); bool nextCrcOk = nextCrc[0] == nextQ[10] && nextCrc[1] == nextQ[11]; fixedAdr = false; // Extraneous bits in ADR if((q[0] & 0xC) != 0) { q[0] &= 0xF3; fixedAdr = true; } CRC16CcittContext.Data(q, 10, out byte[] qCrc); bool status = qCrc[0] == q[10] && qCrc[1] == q[11]; if(fixedAdr && status) return true; int oldAdr = q[0] & 0x3; // Try Q-Mode 1 q[0] = (byte)((q[0] & 0xF0) + 1); CRC16CcittContext.Data(q, 10, out qCrc); status = qCrc[0] == q[10] && qCrc[1] == q[11]; if(status) { fixedAdr = true; return true; } // Try Q-Mode 2 q[0] = (byte)((q[0] & 0xF0) + 2); CRC16CcittContext.Data(q, 10, out qCrc); status = qCrc[0] == q[10] && qCrc[1] == q[11]; if(status) { fixedAdr = true; return true; } // Try Q-Mode 3 q[0] = (byte)((q[0] & 0xF0) + 3); CRC16CcittContext.Data(q, 10, out qCrc); status = qCrc[0] == q[10] && qCrc[1] == q[11]; if(status) { fixedAdr = true; return true; } q[0] = (byte)((q[0] & 0xF0) + oldAdr); oldAdr = q[0]; // Try using previous control if(preCrcOk && (q[0] & 0xF0) != (preQ[0] & 0xF0)) { q[0] = (byte)((q[0] & 0x03) + (preQ[0] & 0xF0)); CRC16CcittContext.Data(q, 10, out qCrc); status = qCrc[0] == q[10] && qCrc[1] == q[11]; if(status) { controlFix = true; return true; } q[0] = (byte)oldAdr; } // Try using next control if(nextCrcOk && (q[0] & 0xF0) != (nextQ[0] & 0xF0)) { q[0] = (byte)((q[0] & 0x03) + (nextQ[0] & 0xF0)); CRC16CcittContext.Data(q, 10, out qCrc); status = qCrc[0] == q[10] && qCrc[1] == q[11]; if(status) { controlFix = true; return true; } q[0] = (byte)oldAdr; } if(preCrcOk && nextCrcOk && (nextQ[0] & 0xF0) == (preQ[0] & 0xF0) && (q[0] & 0xF0) != (nextQ[0] & 0xF0)) { q[0] = (byte)((q[0] & 0x03) + (nextQ[0] & 0xF0)); controlFix = true; } switch(q[0] & 0x3) { // Positioning case 1: { // ZERO not zero if(q[6] != 0) { q[6] = 0; fixedZero = true; CRC16CcittContext.Data(q, 10, out qCrc); status = qCrc[0] == q[10] && qCrc[1] == q[11]; if(status) return true; } if(preCrcOk && nextCrcOk) { if(preQ[1] == nextQ[1] && preQ[1] != q[1]) { q[1] = preQ[1]; fixedTno = true; CRC16CcittContext.Data(q, 10, out qCrc); status = qCrc[0] == q[10] && qCrc[1] == q[11]; if(status) return true; } } if(preCrcOk && nextCrcOk) { if(preQ[2] == nextQ[2] && preQ[2] != q[2]) { q[2] = preQ[2]; fixedIndex = true; CRC16CcittContext.Data(q, 10, out qCrc); status = qCrc[0] == q[10] && qCrc[1] == q[11]; if(status) return true; } } var amin = (byte)(q[7] / 16 * 10 + (q[7] & 0x0F)); var asec = (byte)(q[8] / 16 * 10 + (q[8] & 0x0F)); aframe = (byte)(q[9] / 16 * 10 + (q[9] & 0x0F)); int aPos = amin * 60 * 75 + asec * 75 + aframe - 150; var pmin = (byte)(q[3] / 16 * 10 + (q[3] & 0x0F)); var psec = (byte)(q[4] / 16 * 10 + (q[4] & 0x0F)); var pframe = (byte)(q[5] / 16 * 10 + (q[5] & 0x0F)); int pPos = pmin * 60 * 75 + psec * 75 + pframe; // TODO: pregap // Not pregap byte rmin; byte rsec; int rPos; int dPos; if(q[2] > 0) { // Previous was not pregap either if(preQ[2] > 0 && preCrcOk) { rmin = (byte)(preQ[3] / 16 * 10 + (preQ[3] & 0x0F)); rsec = (byte)(preQ[4] / 16 * 10 + (preQ[4] & 0x0F)); rframe = (byte)(preQ[5] / 16 * 10 + (preQ[5] & 0x0F)); rPos = rmin * 60 * 75 + rsec * 75 + rframe; dPos = pPos - rPos; if(dPos != 1) { q[3] = preQ[3]; q[4] = preQ[4]; q[5] = preQ[5]; // BCD add 1, so 0x39 becomes 0x40 if((q[5] & 0xF) == 9) q[5] += 7; else q[5]++; // 74 frames, so from 0x00 to 0x74, BCD if(q[5] >= 0x74) { // 0 frames q[5] = 0; // Add 1 second if((q[4] & 0xF) == 9) q[4] += 7; else q[4]++; // 60 seconds, so from 0x00 to 0x59, BCD if(q[4] >= 0x59) { // 0 seconds q[4] = 0; // Add 1 minute q[3]++; } } fixedRelPos = true; CRC16CcittContext.Data(q, 10, out qCrc); status = qCrc[0] == q[10] && qCrc[1] == q[11]; if(status) return true; } } // Next is not pregap and we didn't fix relative position with previous if(nextQ[2] > 0 && nextCrcOk && !fixedRelPos) { rmin = (byte)(nextQ[3] / 16 * 10 + (nextQ[3] & 0x0F)); rsec = (byte)(nextQ[4] / 16 * 10 + (nextQ[4] & 0x0F)); rframe = (byte)(nextQ[5] / 16 * 10 + (nextQ[5] & 0x0F)); rPos = rmin * 60 * 75 + rsec * 75 + rframe; dPos = rPos - pPos; if(dPos != 1) { q[3] = nextQ[3]; q[4] = nextQ[4]; q[5] = nextQ[5]; // If frames is 0 if(q[5] == 0) { // If seconds is 0 if(q[4] == 0) { // BCD decrease minutes if((q[3] & 0xF) == 0) q[3] = (byte)((q[3] & 0xF0) - 0x10); else q[3]--; q[4] = 0x59; q[5] = 0x73; } else { // BCD decrease seconds if((q[4] & 0xF) == 0) q[4] = (byte)((q[4] & 0xF0) - 0x10); else q[4]--; q[5] = 0x73; } } // BCD decrease frames else if((q[5] & 0xF) == 0) q[5] = (byte)((q[5] & 0xF0) - 0x10); else q[5]--; fixedRelPos = true; CRC16CcittContext.Data(q, 10, out qCrc); status = qCrc[0] == q[10] && qCrc[1] == q[11]; if(status) return true; } } } // Previous Q's CRC is correct if(preCrcOk) { rmin = (byte)(preQ[7] / 16 * 10 + (preQ[7] & 0x0F)); rsec = (byte)(preQ[8] / 16 * 10 + (preQ[8] & 0x0F)); rframe = (byte)(preQ[9] / 16 * 10 + (preQ[9] & 0x0F)); rPos = rmin * 60 * 75 + rsec * 75 + rframe - 150; dPos = aPos - rPos; if(dPos != 1) { q[7] = preQ[7]; q[8] = preQ[8]; q[9] = preQ[9]; // BCD add 1, so 0x39 becomes 0x40 if((q[9] & 0xF) == 9) q[9] += 7; else q[9]++; // 74 frames, so from 0x00 to 0x74, BCD if(q[9] >= 0x74) { // 0 frames q[9] = 0; // Add 1 second if((q[8] & 0xF) == 9) q[8] += 7; else q[8]++; // 60 seconds, so from 0x00 to 0x59, BCD if(q[8] >= 0x59) { // 0 seconds q[8] = 0; // Add 1 minute q[7]++; } } fixedAbsPos = true; CRC16CcittContext.Data(q, 10, out qCrc); status = qCrc[0] == q[10] && qCrc[1] == q[11]; if(status) return true; } } // Next is not pregap and we didn't fix relative position with previous if(nextQ[2] > 0 && nextCrcOk && !fixedAbsPos) { rmin = (byte)(nextQ[7] / 16 * 10 + (nextQ[7] & 0x0F)); rsec = (byte)(nextQ[8] / 16 * 10 + (nextQ[8] & 0x0F)); rframe = (byte)(nextQ[9] / 16 * 10 + (nextQ[9] & 0x0F)); rPos = rmin * 60 * 75 + rsec * 75 + rframe - 150; dPos = rPos - pPos; if(dPos != 1) { q[7] = nextQ[7]; q[8] = nextQ[8]; q[9] = nextQ[9]; // If frames is 0 if(q[9] == 0) { // If seconds is 0 if(q[8] == 0) { // BCD decrease minutes if((q[7] & 0xF) == 0) q[7] = (byte)((q[7] & 0xF0) - 0x10); else q[7]--; q[8] = 0x59; q[9] = 0x73; } else { // BCD decrease seconds if((q[8] & 0xF) == 0) q[8] = (byte)((q[8] & 0xF0) - 0x10); else q[8]--; q[9] = 0x73; } } // BCD decrease frames else if((q[9] & 0xF) == 0) q[9] = (byte)((q[9] & 0xF0) - 0x10); else q[9]--; fixedAbsPos = true; CRC16CcittContext.Data(q, 10, out qCrc); status = qCrc[0] == q[10] && qCrc[1] == q[11]; if(status) return true; } } CRC16CcittContext.Data(q, 10, out qCrc); status = qCrc[0] == q[10] && qCrc[1] == q[11]; // Game Over if(!fixCrc || status) return false; // Previous Q's CRC is correct if(preCrcOk) { rmin = (byte)(preQ[7] / 16 * 10 + (preQ[7] & 0x0F)); rsec = (byte)(preQ[8] / 16 * 10 + (preQ[8] & 0x0F)); rframe = (byte)(preQ[9] / 16 * 10 + (preQ[9] & 0x0F)); rPos = rmin * 60 * 75 + rsec * 75 + rframe - 150; dPos = aPos - rPos; bool absOk = dPos == 1; rmin = (byte)(preQ[3] / 16 * 10 + (preQ[3] & 0x0F)); rsec = (byte)(preQ[4] / 16 * 10 + (preQ[4] & 0x0F)); rframe = (byte)(preQ[5] / 16 * 10 + (preQ[5] & 0x0F)); rPos = rmin * 60 * 75 + rsec * 75 + rframe; dPos = pPos - rPos; bool relOk = dPos == 1; if(q[0] != preQ[0] || q[1] != preQ[1] || q[2] != preQ[2] || q[6] != 0 || !absOk || !relOk) return false; CRC16CcittContext.Data(q, 10, out qCrc); q[10] = qCrc[0]; q[11] = qCrc[1]; fixedCrc = true; return true; } // Next Q's CRC is correct if(nextCrcOk) { rmin = (byte)(nextQ[7] / 16 * 10 + (nextQ[7] & 0x0F)); rsec = (byte)(nextQ[8] / 16 * 10 + (nextQ[8] & 0x0F)); rframe = (byte)(nextQ[9] / 16 * 10 + (nextQ[9] & 0x0F)); rPos = rmin * 60 * 75 + rsec * 75 + rframe - 150; dPos = rPos - aPos; bool absOk = dPos == 1; rmin = (byte)(nextQ[3] / 16 * 10 + (nextQ[3] & 0x0F)); rsec = (byte)(nextQ[4] / 16 * 10 + (nextQ[4] & 0x0F)); rframe = (byte)(nextQ[5] / 16 * 10 + (nextQ[5] & 0x0F)); rPos = rmin * 60 * 75 + rsec * 75 + rframe; dPos = rPos - pPos; bool relOk = dPos == 1; if(q[0] != nextQ[0] || q[1] != nextQ[1] || q[2] != nextQ[2] || q[6] != 0 || !absOk || !relOk) return false; CRC16CcittContext.Data(q, 10, out qCrc); q[10] = qCrc[0]; q[11] = qCrc[1]; fixedCrc = true; return true; } // Ok if previous and next are both BAD I won't rewrite the CRC at all break; } // MCN case 2: { // Previous Q's CRC is correct if(preCrcOk) { rframe = (byte)(preQ[9] / 16 * 10 + (preQ[9] & 0x0F)); aframe = (byte)(q[9] / 16 * 10 + (q[9] & 0x0F)); if(aframe - rframe != 1) { q[9] = preQ[9]; if((q[9] & 0xF) == 9) q[9] += 7; else q[9]++; if(q[9] >= 0x74) q[9] = 0; fixedAbsPos = true; CRC16CcittContext.Data(q, 10, out qCrc); status = qCrc[0] == q[10] && qCrc[1] == q[11]; if(status) return true; } } // Next Q's CRC is correct else if(nextCrcOk) { rframe = (byte)(nextQ[9] / 16 * 10 + (nextQ[9] & 0x0F)); aframe = (byte)(q[9] / 16 * 10 + (q[9] & 0x0F)); if(aframe - rframe != 1) { q[9] = nextQ[9]; if(q[9] == 0) q[9] = 0x73; else if((q[9] & 0xF) == 0) q[9] = (byte)((q[9] & 0xF0) - 0x10); else q[9]--; fixedAbsPos = true; CRC16CcittContext.Data(q, 10, out qCrc); status = qCrc[0] == q[10] && qCrc[1] == q[11]; if(status) return true; } } // We know the MCN if(mcn != null) { q[1] = (byte)((mcn[0] - 0x30 & 0x0F) * 16 + (mcn[1] - 0x30 & 0x0F)); q[2] = (byte)((mcn[2] - 0x30 & 0x0F) * 16 + (mcn[3] - 0x30 & 0x0F)); q[3] = (byte)((mcn[4] - 0x30 & 0x0F) * 16 + (mcn[5] - 0x30 & 0x0F)); q[4] = (byte)((mcn[6] - 0x30 & 0x0F) * 16 + (mcn[7] - 0x30 & 0x0F)); q[5] = (byte)((mcn[8] - 0x30 & 0x0F) * 16 + (mcn[9] - 0x30 & 0x0F)); q[6] = (byte)((mcn[10] - 0x30 & 0x0F) * 16 + (mcn[11] - 0x30 & 0x0F)); q[7] = (byte)((mcn[12] - 0x30 & 0x0F) * 8); q[8] = 0; fixedMcn = true; CRC16CcittContext.Data(q, 10, out qCrc); status = qCrc[0] == q[10] && qCrc[1] == q[11]; if(status) return true; } if(!fixCrc || !nextCrcOk || !preCrcOk) return false; CRC16CcittContext.Data(q, 10, out qCrc); q[10] = qCrc[0]; q[11] = qCrc[1]; fixedCrc = true; return true; } // ISRC case 3: { // Previous Q's CRC is correct if(preCrcOk) { rframe = (byte)(preQ[9] / 16 * 10 + (preQ[9] & 0x0F)); aframe = (byte)(q[9] / 16 * 10 + (q[9] & 0x0F)); if(aframe - rframe != 1) { q[9] = preQ[9]; if((q[9] & 0xF) == 9) q[9] += 7; else q[9]++; if(q[9] >= 0x74) q[9] = 0; fixedAbsPos = true; CRC16CcittContext.Data(q, 10, out qCrc); status = qCrc[0] == q[10] && qCrc[1] == q[11]; if(status) return true; } } // Next Q's CRC is correct else if(nextCrcOk) { rframe = (byte)(nextQ[9] / 16 * 10 + (nextQ[9] & 0x0F)); aframe = (byte)(q[9] / 16 * 10 + (q[9] & 0x0F)); if(aframe - rframe != 1) { q[9] = nextQ[9]; if(q[9] == 0) q[9] = 0x73; else if((q[9] & 0xF) == 0) q[9] = (byte)((q[9] & 0xF0) - 0x10); else q[9]--; fixedAbsPos = true; CRC16CcittContext.Data(q, 10, out qCrc); status = qCrc[0] == q[10] && qCrc[1] == q[11]; if(status) return true; } } // We know the ISRC if(isrc != null) { byte i1 = Subchannel.GetIsrcCode(isrc[0]); byte i2 = Subchannel.GetIsrcCode(isrc[1]); byte i3 = Subchannel.GetIsrcCode(isrc[2]); byte i4 = Subchannel.GetIsrcCode(isrc[3]); byte i5 = Subchannel.GetIsrcCode(isrc[4]); q[1] = (byte)((i1 << 2) + ((i2 & 0x30) >> 4)); q[2] = (byte)(((i2 & 0xF) << 4) + (i3 >> 2)); q[3] = (byte)(((i3 & 0x3) << 6) + i4); q[4] = (byte)(i5 << 2); q[5] = (byte)((isrc[5] - 0x30 & 0x0F) * 16 + (isrc[6] - 0x30 & 0x0F)); q[6] = (byte)((isrc[7] - 0x30 & 0x0F) * 16 + (isrc[8] - 0x30 & 0x0F)); q[7] = (byte)((isrc[9] - 0x30 & 0x0F) * 16 + (isrc[10] - 0x30 & 0x0F)); q[8] = (byte)((isrc[11] - 0x30 & 0x0F) * 16); fixedIsrc = true; CRC16CcittContext.Data(q, 10, out qCrc); status = qCrc[0] == q[10] && qCrc[1] == q[11]; if(status) return true; } if(!fixCrc || !nextCrcOk || !preCrcOk) return false; CRC16CcittContext.Data(q, 10, out qCrc); q[10] = qCrc[0]; q[11] = qCrc[1]; fixedCrc = true; return true; } } return false; } /// Generates a correct subchannel all the missing ones /// List of missing subchannels /// List of tracks /// Flags of tracks /// Disc size /// Subchannel log /// Progress initialization callback /// Progress update callback /// Progress finalization callback /// Output image public static void GenerateSubchannels(HashSet subchannelExtents, Track[] tracks, Dictionary trackFlags, ulong blocks, SubchannelLog subLog, InitProgressHandler initProgress, UpdateProgressHandler updateProgress, EndProgressHandler endProgress, IWritableImage outputPlugin) { initProgress?.Invoke(); foreach(int sector in subchannelExtents) { Track track = tracks.LastOrDefault(t => (int)t.StartSector <= sector); byte flags; ulong trackStart; ulong pregap; if(track == null) continue; // Hidden track if(track.Sequence == 0) { track = tracks.FirstOrDefault(static t => (int)t.Sequence == 1); trackStart = 0; pregap = track?.StartSector ?? 0; } else { trackStart = track.StartSector; pregap = track.Pregap; } if(!trackFlags.TryGetValue((byte)(track?.Sequence ?? 0), out byte trkFlags) && track?.Type != TrackType.Audio) flags = (byte)CdFlags.DataTrack; else flags = trkFlags; byte index; if(track?.Indexes?.Count > 0) index = (byte)track.Indexes.LastOrDefault(i => i.Value >= sector).Key; else index = 0; updateProgress?.Invoke(string.Format(Localization.Core.Generating_subchannel_for_sector_0, sector), sector, (long)blocks); AaruLogging.WriteLine(string.Format(Localization.Core.Generating_subchannel_for_sector_0, sector)); byte[] sub = Subchannel.Generate(sector, track?.Sequence ?? 0, (int)pregap, (int)trackStart, flags, index); outputPlugin.WriteSectorsTag(sub, (ulong)sector, false, 1, SectorTagType.CdSectorSubchannel); subLog?.WriteEntry(sub, true, sector, 1, true, false); } endProgress?.Invoke(); } }