using System; using System.Collections.Generic; using System.Linq; using Aaru.Checksums; using Aaru.CommonTypes.Enums; using Aaru.CommonTypes.Interfaces; using Aaru.CommonTypes.Structs; using Aaru.Core.Logging; using Aaru.Decoders.CD; using Aaru.Devices; namespace Aaru.Core.Media { public static class CompactDisc { // Return true if indexes have changed 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, IWritableImage outputPlugin, bool fixSubchannel, bool fixSubchannelCrc, DumpLog dumpLog, UpdateStatusHandler updateStatus, Dictionary smallestPregapLbaPerTrack) { if(supportedSubchannel == MmcSubchannel.Q16) sub = Subchannel.ConvertQToRaw(sub); if(!fixSubchannelPosition && desiredSubchannel != MmcSubchannel.None) outputPlugin.WriteSectorsTag(sub, sectorAddress, 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, dumpLog, updateStatus, smallestPregapLbaPerTrack); if(!fixSubchannelPosition || desiredSubchannel == MmcSubchannel.None) return indexesChanged; int prePos = int.MinValue; // Check subchannel for(int subPos = 0; subPos < deSub.Length; subPos += 96) { long lba = (long)sectorAddress + (subPos / 96); bool @fixed = false; byte[] 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]; bool pOk = true; int pWeight = 0; bool rwOk = true; for(int p = subPos; p < subPos + 12; p++) { if(deSub[p] != 0 && deSub[p] != 255) pOk = false; for(int w = 0; w < 8; w++) if(((deSub[p] >> w) & 1) > 0) pWeight++; } for(int rw = subPos + 24; rw < subPos + 96; rw++) { if(deSub[rw] == 0) continue; rwOk = false; break; } bool rwPacket = false; bool cdtextPacket = false; if(!rwOk) { byte[] 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); } 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); } if(!rwOk && !rwPacket && !cdtextPacket && fixSubchannel) { for(int rw = subPos + 24; rw < subPos + 96; rw++) deSub[rw] = 0; rwOk = true; @fixed = true; subLog.WriteRwFix(lba); } byte smin, ssec, amin, asec, aframe; int aPos; 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; aframe = (byte)(((q[9] / 16) * 10) + (q[9] & 0x0F)); if((q[0] & 0x3) == 1) { amin = (byte)(((q[7] / 16) * 10) + (q[7] & 0x0F)); 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; smin = (byte)(expectedSectorAddress / 60 / 75); expectedSectorAddress -= (ulong)(smin * 60 * 75); 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; byte[] posSub = new byte[96]; Array.Copy(deSub, subPos, posSub, 0, 96); posSub = Subchannel.Interleave(posSub); outputPlugin.WriteSectorTag(posSub, (ulong)aPos, SectorTagType.CdSectorSubchannel); subchannelExtents.Remove(aPos); if(@fixed) subLog?.WriteEntry(posSub, supportedSubchannel == MmcSubchannel.Raw, lba, 1, false, true); } return indexesChanged; } static bool CheckIndexesFromSubchannel(byte[] deSub, Dictionary isrcs, byte currentTrack, ref string mcn, Track[] tracks, DumpLog dumpLog, UpdateStatusHandler updateStatus, Dictionary smallestPregapLbaPerTrack) { bool status = false; // Check subchannel for(int subPos = 0; subPos < deSub.Length; subPos += 96) { byte[] 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]; // ISRC if((q[0] & 0x3) == 3) { string isrc = Subchannel.DecodeIsrc(q); if(isrc == null || isrc == "000000000000") continue; if(!crcOk) continue; if(!isrcs.ContainsKey(currentTrack)) { dumpLog?.WriteLine($"Found new ISRC {isrc} for track {currentTrack}."); updateStatus?.Invoke($"Found new ISRC {isrc} for track {currentTrack}."); } else if(isrcs[currentTrack] != isrc) { dumpLog?. WriteLine($"ISRC for track {currentTrack} changed from {isrcs[currentTrack]} to {isrc}."); updateStatus?. Invoke($"ISRC for track {currentTrack} changed from {isrcs[currentTrack]} to {isrc}."); } isrcs[currentTrack] = isrc; } else if((q[0] & 0x3) == 2) { string newMcn = Subchannel.DecodeMcn(q); if(newMcn == null || newMcn == "0000000000000") continue; if(!crcOk) continue; if(mcn is null) { dumpLog?.WriteLine($"Found new MCN {newMcn}."); updateStatus?.Invoke($"Found new MCN {newMcn}."); } else if(mcn != newMcn) { dumpLog?.WriteLine($"MCN changed from {mcn} to {newMcn}."); updateStatus?.Invoke($"MCN changed from {mcn} to {newMcn}."); } mcn = newMcn; } else if((q[0] & 0x3) == 1) { if(!crcOk) continue; byte trackNo = (byte)(((q[1] / 16) * 10) + (q[1] & 0x0F)); for(int i = 0; i < tracks.Length; i++) { if(tracks[i].TrackSequence != trackNo || trackNo == 1) continue; // Pregap if(q[2] == 0) { byte pmin = (byte)(((q[3] / 16) * 10) + (q[3] & 0x0F)); byte psec = (byte)(((q[4] / 16) * 10) + (q[4] & 0x0F)); byte pframe = (byte)(((q[5] / 16) * 10) + (q[5] & 0x0F)); int qPos = (pmin * 60 * 75) + (psec * 75) + pframe; if(!smallestPregapLbaPerTrack.ContainsKey(trackNo)) smallestPregapLbaPerTrack[trackNo] = 1; if(qPos < smallestPregapLbaPerTrack[trackNo]) { int dif = smallestPregapLbaPerTrack[trackNo] - qPos; tracks[i].TrackPregap += (ulong)dif; tracks[i].TrackStartSector -= (ulong)dif; smallestPregapLbaPerTrack[trackNo] = qPos; if(i > 0) tracks[i - 1].TrackEndSector = tracks[i].TrackStartSector - 1; dumpLog?. WriteLine($"Pregap for track {trackNo} set to {tracks[i].TrackPregap} sectors."); updateStatus?. Invoke($"Pregap for track {trackNo} set to {tracks[i].TrackPregap} sectors."); status = true; } if(tracks[i].TrackPregap >= (ulong)qPos) continue; ulong oldPregap = tracks[i].TrackPregap; tracks[i].TrackPregap = (ulong)qPos; tracks[i].TrackStartSector -= tracks[i].TrackPregap - oldPregap; if(i > 0) tracks[i - 1].TrackEndSector = tracks[i].TrackStartSector - 1; dumpLog?.WriteLine($"Pregap for track {trackNo} set to {tracks[i].TrackPregap} sectors."); updateStatus?.Invoke($"Pregap for track {trackNo} set to {tracks[i].TrackPregap} sectors."); status = true; continue; } byte amin = (byte)(((q[7] / 16) * 10) + (q[7] & 0x0F)); byte asec = (byte)(((q[8] / 16) * 10) + (q[8] & 0x0F)); byte aframe = (byte)(((q[9] / 16) * 10) + (q[9] & 0x0F)); int aPos = ((amin * 60 * 75) + (asec * 75) + aframe) - 150; if(tracks[i].Indexes.ContainsKey(q[2]) && aPos >= tracks[i].Indexes[q[2]]) continue; dumpLog?.WriteLine($"Setting index {q[2]} for track {trackNo} to LBA {aPos}."); updateStatus?.Invoke($"Setting index {q[2]} for track {trackNo} to LBA {aPos}."); tracks[i].Indexes[q[2]] = aPos; status = true; } } } return status; } static void DetectRwPackets(byte[] subchannel, out bool zero, out bool rwPacket, out bool cdtextPacket) { zero = false; rwPacket = false; cdtextPacket = false; byte[] cdTextPack1 = new byte[18]; byte[] cdTextPack2 = new byte[18]; byte[] cdTextPack3 = new byte[18]; byte[] cdTextPack4 = new byte[18]; byte[] cdSubRwPack1 = new byte[24]; byte[] cdSubRwPack2 = new byte[24]; byte[] cdSubRwPack3 = new byte[24]; byte[] cdSubRwPack4 = new byte[24]; int i = 0; for(int 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(int 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(int 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(int 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(int j = 0; j < 24; j++) cdSubRwPack1[j] = (byte)(subchannel[i++] & 0x3F); for(int j = 0; j < 24; j++) cdSubRwPack2[j] = (byte)(subchannel[i++] & 0x3F); for(int j = 0; j < 24; j++) cdSubRwPack3[j] = (byte)(subchannel[i++] & 0x3F); for(int 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; } static bool CheckCdTextPackets(byte[] subchannel) { byte[] cdTextPack1 = new byte[18]; byte[] cdTextPack2 = new byte[18]; byte[] cdTextPack3 = new byte[18]; byte[] cdTextPack4 = new byte[18]; int i = 0; for(int 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(int 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(int 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(int 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)); } bool status = true; if((cdTextPack1[0] & 0x80) == 0x80) { ushort cdTextPack1Crc = BigEndianBitConverter.ToUInt16(cdTextPack1, 16); byte[] 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) { ushort cdTextPack2Crc = BigEndianBitConverter.ToUInt16(cdTextPack2, 16); byte[] 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) { ushort cdTextPack3Crc = BigEndianBitConverter.ToUInt16(cdTextPack3, 16); byte[] 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; ushort cdTextPack4Crc = BigEndianBitConverter.ToUInt16(cdTextPack4, 16); byte[] 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; } 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 amin, asec, aframe, pmin, psec, pframe; byte rmin, rsec, rframe; int aPos, rPos, pPos, dPos; controlFix = false; fixedZero = false; fixedTno = false; fixedIndex = false; fixedRelPos = false; fixedAbsPos = false; fixedCrc = false; fixedMcn = false; fixedIsrc = false; byte[] preQ = new byte[12]; byte[] nextQ = new byte[12]; Array.Copy(deSub, (subPos + 12) - 96, preQ, 0, 12); Array.Copy(deSub, subPos + 12 + 96, nextQ, 0, 12); bool status; 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); 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; } if((q[0] & 0x3) == 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; } } amin = (byte)(((q[7] / 16) * 10) + (q[7] & 0x0F)); asec = (byte)(((q[8] / 16) * 10) + (q[8] & 0x0F)); aframe = (byte)(((q[9] / 16) * 10) + (q[9] & 0x0F)); aPos = ((amin * 60 * 75) + (asec * 75) + aframe) - 150; pmin = (byte)(((q[3] / 16) * 10) + (q[3] & 0x0F)); psec = (byte)(((q[4] / 16) * 10) + (q[4] & 0x0F)); pframe = (byte)(((q[5] / 16) * 10) + (q[5] & 0x0F)); pPos = (pmin * 60 * 75) + (psec * 75) + pframe; // TODO: pregap // Not pregap 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; } } } 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; 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; } 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 } else if((q[0] & 0x3) == 2) { 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; } } 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; } } 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; } else if((q[0] & 0x3) == 3) { 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; } } 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; } } 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; } public static void GenerateSubchannels(HashSet subchannelExtents, Track[] tracks, Dictionary trackFlags, ulong blocks, SubchannelLog subLog, DumpLog dumpLog, InitProgressHandler initProgress, UpdateProgressHandler updateProgress, EndProgressHandler endProgress, IWritableImage outputPlugin) { initProgress?.Invoke(); foreach(int sector in subchannelExtents) { Track track = tracks.LastOrDefault(t => (int)t.TrackStartSector <= sector); byte trkFlags; byte flags; ulong trackStart; ulong pregap; // Hidden track if(track.TrackSequence == 0) { track = tracks.FirstOrDefault(t => (int)t.TrackSequence == 1); trackStart = 0; pregap = track.TrackStartSector; } else { trackStart = track.TrackStartSector; pregap = track.TrackPregap; } if(!trackFlags.TryGetValue((byte)track.TrackSequence, out trkFlags) && track.TrackType != 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($"Generating subchannel for sector {sector}...", sector, (long)blocks); dumpLog?.WriteLine($"Generating subchannel for sector {sector}."); byte[] sub = Subchannel.Generate(sector, track.TrackSequence, (int)pregap, (int)trackStart, flags, index); outputPlugin.WriteSectorsTag(sub, (ulong)sector, 1, SectorTagType.CdSectorSubchannel); subLog.WriteEntry(sub, true, sector, 1, true, false); } endProgress?.Invoke(); } } }