public static bool SupportsPqSubchannel(Device dev, DumpLog dumpLog, UpdateStatusHandler updateStatus) { dumpLog?.WriteLine("Checking if drive supports PQ subchannel reading..."); updateStatus?.Invoke("Checking if drive supports PQ subchannel reading..."); return(!dev.ReadCd(out _, out _, 0, 2352 + 16, 1, MmcSectorTypes.AllTypes, false, false, true, MmcHeaderCodes.AllHeaders, true, true, MmcErrorField.None, MmcSubchannel.Q16, dev.Timeout, out _)); }
public async void Handle_UpdateStatus_Stop() { var updateStatusHandler = new UpdateStatusHandler(StateManager.Object); await updateStatusHandler.Handle( new FORFarm.Application.Status.UpdateStatus.UpdateStatus() { Running = false }, CancellationToken.None); StateManager.Verify(s => s.Stop()); }
public static void SolveTrackPregaps(Device dev, DumpLog dumpLog, UpdateStatusHandler updateStatus, Track[] tracks, bool supportsPqSubchannel, bool supportsRwSubchannel, Database.Models.Device dbDev, out bool inexactPositioning) { bool sense = true; // Sense indicator byte[] subBuf = null; int posQ; uint retries; bool? bcd = null; byte[] crc; Dictionary<uint, int> pregaps = new Dictionary<uint, int>(); inexactPositioning = false; if(!supportsPqSubchannel && !supportsRwSubchannel) return; // Check if subchannel is BCD for(retries = 0; retries < 10; retries++) { sense = supportsRwSubchannel ? GetSectorForPregapRaw(dev, 11, dbDev, out subBuf, false) : GetSectorForPregapQ16(dev, 11, dbDev, out subBuf, false); if(sense) continue; bcd = (subBuf[9] & 0x10) > 0; break; } AaruConsole.DebugWriteLine("Pregap calculator", bcd == true ? "Subchannel is BCD" : bcd == false ? "Subchannel is not BCD" : "Could not detect drive subchannel BCD"); if(bcd is null) { dumpLog?.WriteLine("Could not detect if drive subchannel is BCD or not, pregaps could not be calculated, dump may be incorrect..."); updateStatus?. Invoke("Could not detect if drive subchannel is BCD or not, pregaps could not be calculated, dump may be incorrect..."); return; } // Initialize the dictionary for(int i = 0; i < tracks.Length; i++) pregaps[tracks[i].TrackSequence] = 0; for(int t = 0; t < tracks.Length; t++) { Track track = tracks[t]; int trackRetries = 0; // First track of each session has at least 150 sectors of pregap and is not readable always if(tracks.Where(t => t.TrackSession == track.TrackSession).OrderBy(t => t.TrackSequence). FirstOrDefault().TrackSequence == track.TrackSequence) { AaruConsole.DebugWriteLine("Pregap calculator", "Skipping track {0}", track.TrackSequence); if(track.TrackSequence > 1) pregaps[track.TrackSequence] = 150; continue; } if(t > 0 && tracks[t - 1].TrackType == tracks[t].TrackType) { AaruConsole.DebugWriteLine("Pregap calculator", "Skipping track {0}", track.TrackSequence); continue; } AaruConsole.DebugWriteLine("Pregap calculator", "Track {0}", track.TrackSequence); int lba = (int)track.TrackStartSector - 1; bool pregapFound = false; Track previousTrack = tracks.FirstOrDefault(t => t.TrackSequence == track.TrackSequence - 1); bool goneBack = false; bool goFront = false; bool forward = false; bool crcOk = false; bool previousPregapIsPreviousTrack = false; // Check if pregap is 0 for(retries = 0; retries < 10 && !pregapFound; retries++) { sense = supportsRwSubchannel ? GetSectorForPregapRaw(dev, (uint)lba, dbDev, out subBuf, track.TrackType == TrackType.Audio) : GetSectorForPregapQ16(dev, (uint)lba, dbDev, out subBuf, track.TrackType == TrackType.Audio); if(sense) { AaruConsole.DebugWriteLine("Pregap calculator", "LBA: {0}, Try {1}, Sense {2}", lba, retries + 1, sense); continue; } if(bcd == false) BinaryToBcdQ(subBuf); CRC16CCITTContext.Data(subBuf, 10, out crc); AaruConsole.DebugWriteLine("Pregap calculator", "LBA: {0}, Try {1}, Sense {2}, Q: {3:X2} {4:X2} {5:X2} {6:X2} {7:X2} {8:X2} {9:X2} {10:X2} {11:X2} {12:X2} CRC 0x{13:X2}{14:X2}, Calculated CRC: 0x{15:X2}{16:X2}", lba, retries + 1, sense, subBuf[0], subBuf[1], subBuf[2], subBuf[3], subBuf[4], subBuf[5], subBuf[6], subBuf[7], subBuf[8], subBuf[9], subBuf[10], subBuf[11], crc[0], crc[1]); crcOk = crc[0] == subBuf[10] && crc[1] == subBuf[11]; // Try to do a simple correction if(!crcOk) { // Data track cannot have 11xxb in CONTROL if((subBuf[0] & 0x40) > 0) subBuf[0] &= 0x7F; // ADR only uses two bits subBuf[0] &= 0xF3; // Don't care about other Q modes if((subBuf[0] & 0xF) == 1) { // ZERO only used in DDCD subBuf[6] = 0; // Fix BCD numbering for(int i = 1; i < 10; i++) { if((subBuf[i] & 0xF0) > 0xA0) subBuf[i] &= 0x7F; if((subBuf[i] & 0x0F) > 0x0A) subBuf[i] &= 0xF7; } } CRC16CCITTContext.Data(subBuf, 10, out crc); crcOk = crc[0] == subBuf[10] && crc[1] == subBuf[11]; if(crcOk) { AaruConsole.DebugWriteLine("Pregap calculator", "LBA: {0}, Try {1}, Sense {2}, Q (FIXED): {3:X2} {4:X2} {5:X2} {6:X2} {7:X2} {8:X2} {9:X2} {10:X2} {11:X2} {12:X2} CRC 0x{13:X2}{14:X2}, Calculated CRC: 0x{15:X2}{16:X2}", lba, retries + 1, sense, subBuf[0], subBuf[1], subBuf[2], subBuf[3], subBuf[4], subBuf[5], subBuf[6], subBuf[7], subBuf[8], subBuf[9], subBuf[10], subBuf[11], crc[0], crc[1]); } else continue; } BcdToBinaryQ(subBuf); // Q position if((subBuf[0] & 0xF) != 1) continue; posQ = ((subBuf[7] * 60 * 75) + (subBuf[8] * 75) + subBuf[9]) - 150; if(subBuf[1] != track.TrackSequence - 1 || subBuf[2] == 0 || posQ != lba) break; pregaps[track.TrackSequence] = 0; pregapFound = true; } if(pregapFound) continue; // Calculate pregap lba = (int)track.TrackStartSector - 150; while(lba > (int)previousTrack.TrackStartSector && lba <= (int)track.TrackStartSector) { // Some drives crash if you try to read just before the previous read, so seek away first if(!forward) sense = supportsRwSubchannel ? GetSectorForPregapRaw(dev, (uint)lba - 10, dbDev, out subBuf, track.TrackType == TrackType.Audio) : GetSectorForPregapQ16(dev, (uint)lba - 10, dbDev, out subBuf, track.TrackType == TrackType.Audio); for(retries = 0; retries < 10; retries++) { sense = supportsRwSubchannel ? GetSectorForPregapRaw(dev, (uint)lba, dbDev, out subBuf, track.TrackType == TrackType.Audio) : GetSectorForPregapQ16(dev, (uint)lba, dbDev, out subBuf, track.TrackType == TrackType.Audio); if(sense) continue; if(bcd == false) BinaryToBcdQ(subBuf); CRC16CCITTContext.Data(subBuf, 10, out crc); AaruConsole.DebugWriteLine("Pregap calculator", "LBA: {0}, Try {1}, Sense {2}, Q: {3:X2} {4:X2} {5:X2} {6:X2} {7:X2} {8:X2} {9:X2} {10:X2} {11:X2} {12:X2} CRC 0x{13:X2}{14:X2}, Calculated CRC: 0x{15:X2}{16:X2}", lba, retries + 1, sense, subBuf[0], subBuf[1], subBuf[2], subBuf[3], subBuf[4], subBuf[5], subBuf[6], subBuf[7], subBuf[8], subBuf[9], subBuf[10], subBuf[11], crc[0], crc[1]); crcOk = crc[0] == subBuf[10] && crc[1] == subBuf[11]; // Try to do a simple correction if(!crcOk) { // Data track cannot have 11xxb in CONTROL if((subBuf[0] & 0x40) > 0) subBuf[0] &= 0x7F; // ADR only uses two bits subBuf[0] &= 0xF3; // Don't care about other Q modes if((subBuf[0] & 0xF) == 1) { // ZERO only used in DDCD subBuf[6] = 0; // Fix BCD numbering for(int i = 1; i < 10; i++) { if((subBuf[i] & 0xF0) > 0xA0) subBuf[i] &= 0x7F; if((subBuf[i] & 0x0F) > 0x0A) subBuf[i] &= 0xF7; } } CRC16CCITTContext.Data(subBuf, 10, out crc); crcOk = crc[0] == subBuf[10] && crc[1] == subBuf[11]; if(crcOk) { AaruConsole.DebugWriteLine("Pregap calculator", "LBA: {0}, Try {1}, Sense {2}, Q (FIXED): {3:X2} {4:X2} {5:X2} {6:X2} {7:X2} {8:X2} {9:X2} {10:X2} {11:X2} {12:X2} CRC 0x{13:X2}{14:X2}, Calculated CRC: 0x{15:X2}{16:X2}", lba, retries + 1, sense, subBuf[0], subBuf[1], subBuf[2], subBuf[3], subBuf[4], subBuf[5], subBuf[6], subBuf[7], subBuf[8], subBuf[9], subBuf[10], subBuf[11], crc[0], crc[1]); break; } } if(crcOk) break; } if(retries == 10) { if(sense) { trackRetries++; if(trackRetries >= 10) { if(pregaps[track.TrackSequence] == 0) { if((previousTrack.TrackType == TrackType.Audio && track.TrackType != TrackType.Audio) || (previousTrack.TrackType != TrackType.Audio && track.TrackType == TrackType.Audio)) { dumpLog?. WriteLine("Could not read subchannel for this track, supposing 150 sectors."); updateStatus?. Invoke("Could not read subchannel for this track, supposing 150 sectors."); } else { dumpLog?. WriteLine("Could not read subchannel for this track, supposing 0 sectors."); updateStatus?. Invoke("Could not read subchannel for this track, supposing 0 sectors."); } } else { dumpLog?. WriteLine($"Could not read subchannel for this track, supposing {pregaps[track.TrackSequence]} sectors."); updateStatus?. Invoke($"Could not read subchannel for this track, supposing {pregaps[track.TrackSequence]} sectors."); } break; } dumpLog?.WriteLine($"Could not read subchannel for sector {lba}"); updateStatus?.Invoke($"Could not read subchannel for sector {lba}"); lba++; forward = true; continue; } dumpLog?.WriteLine($"Could not get correct subchannel for sector {lba}"); updateStatus?.Invoke($"Could not get correct subchannel for sector {lba}"); } if(subBuf.All(b => b == 0)) { inexactPositioning = true; AaruConsole.DebugWriteLine("Pregap calculator", "All Q empty for LBA {0}", lba); break; } BcdToBinaryQ(subBuf); // If it's not Q position if((subBuf[0] & 0xF) != 1) { // This means we already searched back, so search forward if(goFront) { lba++; forward = true; if(lba == (int)previousTrack.TrackStartSector) break; continue; } // Search back goneBack = true; lba--; forward = false; continue; } // Previous track if(subBuf[1] < track.TrackSequence) { lba++; forward = true; previousPregapIsPreviousTrack = true; // Already gone back, so go forward if(goneBack) goFront = true; continue; } // Same track, but not pregap if(subBuf[1] == track.TrackSequence && subBuf[2] > 0) { lba--; forward = false; if(previousPregapIsPreviousTrack) break; continue; } previousPregapIsPreviousTrack = false; // Pregap according to Q position posQ = ((subBuf[7] * 60 * 75) + (subBuf[8] * 75) + subBuf[9]) - 150; int diff = posQ - lba; int pregapQ = (int)track.TrackStartSector - lba; if(diff != 0) { AaruConsole.DebugWriteLine("Pregap calculator", "Invalid Q position for LBA {0}, got {1}", lba, posQ); inexactPositioning = true; } // Received a Q post the LBA we wanted, just go back. If we are already going forward, break if(posQ > lba) { if(forward) break; lba--; continue; } // Bigger than known change, otherwise we found it if(pregapQ > pregaps[track.TrackSequence]) { // If CRC is not OK, only accept pregaps less than 10 sectors longer than previously now if(crcOk || pregapQ - pregaps[track.TrackSequence] < 10) { AaruConsole.DebugWriteLine("Pregap calculator", "Pregap for track {0}: {1}", track.TrackSequence, pregapQ); pregaps[track.TrackSequence] = pregapQ; } // We are going forward, so we have already been in the previous track, so add 1 to pregap and get out of here else if(forward) { pregaps[track.TrackSequence]++; break; } } else if(pregapQ == pregaps[track.TrackSequence]) break; lba--; forward = false; } } for(int i = 0; i < tracks.Length; i++) { tracks[i].TrackPregap = (ulong)pregaps[tracks[i].TrackSequence]; tracks[i].TrackStartSector -= tracks[i].TrackPregap; #if DEBUG dumpLog?.WriteLine($"Track {tracks[i].TrackSequence} pregap is {tracks[i].TrackPregap} sectors"); updateStatus?.Invoke($"Track {tracks[i].TrackSequence} pregap is {tracks[i].TrackPregap} sectors"); #endif } }
public static void GetOffset(CdOffset cdOffset, Device dbDev, bool debug, Aaru.Devices.Device dev, MediaType dskType, DumpLog dumpLog, Track[] tracks, UpdateStatusHandler updateStatus, out int?driveOffset, out int?combinedOffset, out bool supportsPlextorReadCdDa) { byte[] cmdBuf; bool sense; int minute; int second; int frame; byte[] sectorSync; byte[] tmpBuf; int lba; int diff; Track dataTrack = default; Track audioTrack = default; bool offsetFound = false; const uint sectorSize = 2352; driveOffset = cdOffset?.Offset * 4; combinedOffset = null; supportsPlextorReadCdDa = false; if (dskType != MediaType.VideoNowColor) { if (tracks.Any(t => t.TrackType != TrackType.Audio)) { dataTrack = tracks.FirstOrDefault(t => t.TrackType != TrackType.Audio); if (dataTrack != null) { // Build sync sectorSync = new byte[] { 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00 }; tmpBuf = new byte[sectorSync.Length]; // Ensure to be out of the pregap, or multi-session discs give funny values uint wantedLba = (uint)(dataTrack.TrackStartSector + 151); // Plextor READ CDDA if (dbDev?.ATAPI?.RemovableMedias?.Any(d => d.SupportsPlextorReadCDDA == true) == true || dbDev?.SCSI?.RemovableMedias?.Any(d => d.SupportsPlextorReadCDDA == true) == true || dev.Manufacturer.ToLowerInvariant() == "plextor") { sense = dev.PlextorReadCdDa(out cmdBuf, out _, wantedLba, sectorSize, 3, PlextorSubchannel.None, dev.Timeout, out _); if (!sense && !dev.Error) { supportsPlextorReadCdDa = true; for (int i = 0; i < cmdBuf.Length - sectorSync.Length; i++) { Array.Copy(cmdBuf, i, tmpBuf, 0, sectorSync.Length); if (!tmpBuf.SequenceEqual(sectorSync)) { continue; } // De-scramble M and S minute = cmdBuf[i + 12] ^ 0x01; second = cmdBuf[i + 13] ^ 0x80; frame = cmdBuf[i + 14]; // Convert to binary minute = ((minute / 16) * 10) + (minute & 0x0F); second = ((second / 16) * 10) + (second & 0x0F); frame = ((frame / 16) * 10) + (frame & 0x0F); // Calculate the first found LBA lba = ((minute * 60 * 75) + (second * 75) + frame) - 150; // Calculate the difference between the found LBA and the requested one diff = (int)wantedLba - lba; combinedOffset = i + (2352 * diff); offsetFound = true; break; } } } if (!offsetFound && (debug || dbDev?.ATAPI?.RemovableMedias?.Any(d => d.CanReadCdScrambled == true) == true || dbDev?.SCSI?.RemovableMedias?.Any(d => d.CanReadCdScrambled == true) == true || dbDev?.SCSI?.MultiMediaDevice?.TestedMedia?.Any(d => d.CanReadCdScrambled == true) == true || dev.Manufacturer.ToLowerInvariant() == "hl-dt-st")) { sense = dev.ReadCd(out cmdBuf, out _, wantedLba, sectorSize, 3, MmcSectorTypes.Cdda, false, false, false, MmcHeaderCodes.None, true, false, MmcErrorField.None, MmcSubchannel.None, dev.Timeout, out _); if (!sense && !dev.Error) { // Clear cache for (int i = 0; i < 63; i++) { sense = dev.ReadCd(out _, out _, (uint)(wantedLba + 3 + (16 * i)), sectorSize, 16, MmcSectorTypes.AllTypes, false, false, false, MmcHeaderCodes.None, true, false, MmcErrorField.None, MmcSubchannel.None, dev.Timeout, out _); if (sense || dev.Error) { break; } } dev.ReadCd(out cmdBuf, out _, wantedLba, sectorSize, 3, MmcSectorTypes.Cdda, false, false, false, MmcHeaderCodes.None, true, false, MmcErrorField.None, MmcSubchannel.None, dev.Timeout, out _); for (int i = 0; i < cmdBuf.Length - sectorSync.Length; i++) { Array.Copy(cmdBuf, i, tmpBuf, 0, sectorSync.Length); if (!tmpBuf.SequenceEqual(sectorSync)) { continue; } // De-scramble M and S minute = cmdBuf[i + 12] ^ 0x01; second = cmdBuf[i + 13] ^ 0x80; frame = cmdBuf[i + 14]; // Convert to binary minute = ((minute / 16) * 10) + (minute & 0x0F); second = ((second / 16) * 10) + (second & 0x0F); frame = ((frame / 16) * 10) + (frame & 0x0F); // Calculate the first found LBA lba = ((minute * 60 * 75) + (second * 75) + frame) - 150; // Calculate the difference between the found LBA and the requested one diff = (int)wantedLba - lba; combinedOffset = i + (2352 * diff); offsetFound = true; break; } } } } } if (offsetFound) { return; } // Try to get another the offset some other way, we need an audio track just after a data track, same session for (int i = 1; i < tracks.Length; i++) { if (tracks[i - 1].TrackType == TrackType.Audio || tracks[i].TrackType != TrackType.Audio) { continue; } dataTrack = tracks[i - 1]; audioTrack = tracks[i]; break; } if (dataTrack is null || audioTrack is null) { return; } // Found them sense = dev.ReadCd(out cmdBuf, out _, (uint)audioTrack.TrackStartSector, sectorSize, 3, MmcSectorTypes.Cdda, false, false, false, MmcHeaderCodes.None, true, false, MmcErrorField.None, MmcSubchannel.None, dev.Timeout, out _); if (sense || dev.Error) { return; } dataTrack.TrackEndSector += 150; // Calculate MSF minute = (int)dataTrack.TrackEndSector / 4500; second = ((int)dataTrack.TrackEndSector - (minute * 4500)) / 75; frame = (int)dataTrack.TrackEndSector - (minute * 4500) - (second * 75); dataTrack.TrackEndSector -= 150; // Convert to BCD minute = ((minute / 10) << 4) + (minute % 10); second = ((second / 10) << 4) + (second % 10); frame = ((frame / 10) << 4) + (frame % 10); // Scramble M and S minute ^= 0x01; second ^= 0x80; // Build sync sectorSync = new byte[] { 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, (byte)minute, (byte)second, (byte)frame }; tmpBuf = new byte[sectorSync.Length]; for (int i = 0; i < cmdBuf.Length - sectorSync.Length; i++) { Array.Copy(cmdBuf, i, tmpBuf, 0, sectorSync.Length); if (!tmpBuf.SequenceEqual(sectorSync)) { continue; } combinedOffset = i + 2352; offsetFound = true; break; } if (offsetFound || audioTrack.TrackPregap <= 0) { return; } sense = dev.ReadCd(out byte[] dataBuf, out _, (uint)dataTrack.TrackEndSector, sectorSize, 1, MmcSectorTypes.AllTypes, false, false, true, MmcHeaderCodes.AllHeaders, true, true, MmcErrorField.None, MmcSubchannel.None, dev.Timeout, out _); if (sense || dev.Error) { return; } for (int i = 0; i < dataBuf.Length; i++) { dataBuf[i] ^= Sector.ScrambleTable[i]; } for (int i = 0; i < 2352; i++) { byte[] dataSide = new byte[2352 - i]; byte[] audioSide = new byte[2352 - i]; Array.Copy(dataBuf, i, dataSide, 0, dataSide.Length); Array.Copy(cmdBuf, 0, audioSide, 0, audioSide.Length); if (!dataSide.SequenceEqual(audioSide)) { continue; } combinedOffset = audioSide.Length; break; } } else { byte[] videoNowColorFrame = new byte[9 * sectorSize]; sense = dev.ReadCd(out cmdBuf, out _, 0, sectorSize, 9, MmcSectorTypes.AllTypes, false, false, true, MmcHeaderCodes.AllHeaders, true, true, MmcErrorField.None, MmcSubchannel.None, dev.Timeout, out _); if (sense || dev.Error) { sense = dev.ReadCd(out cmdBuf, out _, 0, sectorSize, 9, MmcSectorTypes.Cdda, false, false, true, MmcHeaderCodes.None, true, true, MmcErrorField.None, MmcSubchannel.None, dev.Timeout, out _); if (sense || dev.Error) { videoNowColorFrame = null; } } if (videoNowColorFrame is null) { dumpLog?.WriteLine("Could not find VideoNow Color frame offset, dump may not be correct."); updateStatus?.Invoke("Could not find VideoNow Color frame offset, dump may not be correct."); } else { combinedOffset = MMC.GetVideoNowColorOffset(videoNowColorFrame); dumpLog?.WriteLine($"VideoNow Color frame is offset {combinedOffset} bytes."); updateStatus?.Invoke($"VideoNow Color frame is offset {combinedOffset} bytes."); } } }
/// <summary>Reads the TOC, processes it, returns the track list and last sector</summary> /// <param name="dev">Device</param> /// <param name="dumpLog">Dump log</param> /// <param name="force">Force dump enabled</param> /// <param name="lastSector">Last sector number</param> /// <param name="leadOutStarts">Lead-out starts</param> /// <param name="mediaTags">Media tags</param> /// <param name="stoppingErrorMessage">Stopping error message handler</param> /// <param name="toc">Full CD TOC</param> /// <param name="trackFlags">Track flags</param> /// <param name="updateStatus">Update status handler</param> /// <returns>List of tracks</returns> public static Track[] GetCdTracks(Device dev, DumpLog dumpLog, bool force, out long lastSector, Dictionary <int, long> leadOutStarts, Dictionary <MediaTagType, byte[]> mediaTags, ErrorMessageHandler stoppingErrorMessage, out FullTOC.CDFullTOC?toc, Dictionary <byte, byte> trackFlags, UpdateStatusHandler updateStatus) { byte[] cmdBuf; // Data buffer const uint sectorSize = 2352; // Full sector size bool sense; // Sense indicator List <Track> trackList = new List <Track>(); // Tracks in disc byte[] tmpBuf; // Temporary buffer toc = null; lastSector = 0; TrackType leadoutTrackType = TrackType.Audio; // We discarded all discs that falsify a TOC before requesting a real TOC // No TOC, no CD (or an empty one) dumpLog?.WriteLine("Reading full TOC"); updateStatus?.Invoke("Reading full TOC"); sense = dev.ReadRawToc(out cmdBuf, out _, 0, dev.Timeout, out _); if (!sense) { toc = FullTOC.Decode(cmdBuf); if (toc.HasValue) { tmpBuf = new byte[cmdBuf.Length - 2]; Array.Copy(cmdBuf, 2, tmpBuf, 0, cmdBuf.Length - 2); mediaTags?.Add(MediaTagType.CD_FullTOC, tmpBuf); } } updateStatus?.Invoke("Building track map..."); dumpLog?.WriteLine("Building track map..."); if (toc.HasValue) { FullTOC.TrackDataDescriptor[] sortedTracks = toc.Value.TrackDescriptors.OrderBy(track => track.POINT).ToArray(); foreach (FullTOC.TrackDataDescriptor trk in sortedTracks.Where(trk => trk.ADR == 1 || trk.ADR == 4)) { if (trk.POINT >= 0x01 && trk.POINT <= 0x63) { trackList.Add(new Track { TrackSequence = trk.POINT, TrackSession = trk.SessionNumber, TrackType = (TocControl)(trk.CONTROL & 0x0D) == TocControl.DataTrack || (TocControl)(trk.CONTROL & 0x0D) == TocControl.DataTrackIncremental ? TrackType.Data : TrackType.Audio, TrackStartSector = (ulong)((trk.PHOUR * 3600 * 75) + (trk.PMIN * 60 * 75) + (trk.PSEC * 75) + trk.PFRAME - 150), TrackBytesPerSector = (int)sectorSize, TrackRawBytesPerSector = (int)sectorSize }); trackFlags?.Add(trk.POINT, trk.CONTROL); } else if (trk.POINT == 0xA2) { int phour, pmin, psec, pframe; if (trk.PFRAME == 0) { pframe = 74; if (trk.PSEC == 0) { psec = 59; if (trk.PMIN == 0) { pmin = 59; phour = trk.PHOUR - 1; } else { pmin = trk.PMIN - 1; phour = trk.PHOUR; } } else { psec = trk.PSEC - 1; pmin = trk.PMIN; phour = trk.PHOUR; } } else { pframe = trk.PFRAME - 1; psec = trk.PSEC; pmin = trk.PMIN; phour = trk.PHOUR; } lastSector = (phour * 3600 * 75) + (pmin * 60 * 75) + (psec * 75) + pframe - 150; leadOutStarts?.Add(trk.SessionNumber, lastSector + 1); } else if (trk.POINT == 0xA0 && trk.ADR == 1) { leadoutTrackType = (TocControl)(trk.CONTROL & 0x0D) == TocControl.DataTrack || (TocControl)(trk.CONTROL & 0x0D) == TocControl.DataTrackIncremental ? TrackType.Data : TrackType.Audio; } } } else { updateStatus?.Invoke("Cannot read RAW TOC, requesting processed one..."); dumpLog?.WriteLine("Cannot read RAW TOC, requesting processed one..."); sense = dev.ReadToc(out cmdBuf, out _, false, 0, dev.Timeout, out _); TOC.CDTOC?oldToc = TOC.Decode(cmdBuf); if ((sense || !oldToc.HasValue) && !force) { dumpLog?.WriteLine("Could not read TOC, if you want to continue, use force, and will try from LBA 0 to 360000..."); stoppingErrorMessage?. Invoke("Could not read TOC, if you want to continue, use force, and will try from LBA 0 to 360000..."); return(null); } if (oldToc.HasValue) { foreach (TOC.CDTOCTrackDataDescriptor trk in oldToc.Value.TrackDescriptors. OrderBy(t => t.TrackNumber). Where(trk => trk.ADR == 1 || trk.ADR == 4)) { if (trk.TrackNumber >= 0x01 && trk.TrackNumber <= 0x63) { trackList.Add(new Track { TrackSequence = trk.TrackNumber, TrackSession = 1, TrackType = (TocControl)(trk.CONTROL & 0x0D) == TocControl.DataTrack || (TocControl)(trk.CONTROL & 0x0D) == TocControl.DataTrackIncremental ? TrackType.Data : TrackType.Audio, TrackStartSector = trk.TrackStartAddress, TrackBytesPerSector = (int)sectorSize, TrackRawBytesPerSector = (int)sectorSize }); trackFlags?.Add(trk.TrackNumber, trk.CONTROL); } else if (trk.TrackNumber == 0xAA) { leadoutTrackType = (TocControl)(trk.CONTROL & 0x0D) == TocControl.DataTrack || (TocControl)(trk.CONTROL & 0x0D) == TocControl.DataTrackIncremental ? TrackType.Data : TrackType.Audio; lastSector = trk.TrackStartAddress - 1; } } } } if (trackList.Count == 0) { updateStatus?.Invoke("No tracks found, adding a single track from 0 to Lead-Out"); dumpLog?.WriteLine("No tracks found, adding a single track from 0 to Lead-Out"); trackList.Add(new Track { TrackSequence = 1, TrackSession = 1, TrackType = leadoutTrackType, TrackStartSector = 0, TrackBytesPerSector = (int)sectorSize, TrackRawBytesPerSector = (int)sectorSize }); trackFlags?.Add(1, (byte)(leadoutTrackType == TrackType.Audio ? 0 : 4)); } if (lastSector != 0) { return(trackList.ToArray()); } sense = dev.ReadCapacity16(out cmdBuf, out _, dev.Timeout, out _); if (!sense) { byte[] temp = new byte[8]; Array.Copy(cmdBuf, 0, temp, 0, 8); Array.Reverse(temp); lastSector = (long)BitConverter.ToUInt64(temp, 0); } else { sense = dev.ReadCapacity(out cmdBuf, out _, dev.Timeout, out _); if (!sense) { lastSector = (cmdBuf[0] << 24) + (cmdBuf[1] << 16) + (cmdBuf[2] << 8) + cmdBuf[3]; } } if (lastSector > 0) { return(trackList.ToArray()); } if (!force) { stoppingErrorMessage?. Invoke("Could not find Lead-Out, if you want to continue use force option and will continue until 360000 sectors..."); dumpLog?.WriteLine("Could not find Lead-Out, if you want to continue use force option and will continue until 360000 sectors..."); return(null); } updateStatus?. Invoke("WARNING: Could not find Lead-Out start, will try to read up to 360000 sectors, probably will fail before..."); dumpLog?.WriteLine("WARNING: Could not find Lead-Out start, will try to read up to 360000 sectors, probably will fail before..."); lastSector = 360000; return(trackList.ToArray()); }
public static void SolveTrackPregaps(Device dev, DumpLog dumpLog, UpdateStatusHandler updateStatus, Track[] tracks, bool supportsPqSubchannel, bool supportsRwSubchannel, Database.Models.Device dbDev, out bool inexactPositioning) { bool sense; // Sense indicator byte[] subBuf; int posQ; uint retries; bool? bcd = null; byte[] crc; Dictionary <uint, int> pregaps = new Dictionary <uint, int>(); inexactPositioning = false; if (!supportsPqSubchannel && !supportsRwSubchannel) { return; } // Check if subchannel is BCD for (retries = 0; retries < 10; retries++) { sense = supportsRwSubchannel ? GetSectorForPregapRaw(dev, 11, dbDev, out subBuf) : GetSectorForPregapQ16(dev, 11, dbDev, out subBuf); if (sense) { continue; } bcd = (subBuf[9] & 0x10) > 0; break; } if (bcd is null) { dumpLog?.WriteLine("Could not detect if drive subchannel is BCD or not, pregaps could not be calculated, dump may be incorrect..."); updateStatus?. Invoke("Could not detect if drive subchannel is BCD or not, pregaps could not be calculated, dump may be incorrect..."); return; } // Initialize the dictionary for (int i = 0; i < tracks.Length; i++) { pregaps[tracks[i].TrackSequence] = 0; } foreach (Track track in tracks) { if (track.TrackSequence <= 1) { continue; } int lba = (int)track.TrackStartSector - 1; bool pregapFound = false; Track previousTrack = tracks.FirstOrDefault(t => t.TrackSequence == track.TrackSequence - 1); bool goneBack = false; bool goFront = false; // Check if pregap is 0 for (retries = 0; retries < 10; retries++) { sense = supportsRwSubchannel ? GetSectorForPregapRaw(dev, (uint)lba, dbDev, out subBuf) : GetSectorForPregapQ16(dev, (uint)lba, dbDev, out subBuf); if (sense) { continue; } if (bcd == false) { BinaryToBcdQ(subBuf); } CRC16CCITTContext.Data(subBuf, 10, out crc); if (crc[0] != subBuf[10] || crc[1] != subBuf[11]) { continue; } BcdToBinaryQ(subBuf); // Q position if ((subBuf[0] & 0xF) != 1) { continue; } posQ = ((subBuf[7] * 60 * 75) + (subBuf[8] * 75) + subBuf[9]) - 150; if (subBuf[1] != track.TrackSequence - 1 || subBuf[2] == 0 || posQ != lba) { break; } pregaps[track.TrackSequence] = 0; pregapFound = true; } if (pregapFound) { continue; } // Calculate pregap lba = (int)track.TrackStartSector - 150; while (lba > (int)previousTrack.TrackStartSector) { // Some drives crash if you try to read just before the previous read, so seek away first sense = supportsRwSubchannel ? GetSectorForPregapRaw(dev, (uint)lba - 10, dbDev, out subBuf) : GetSectorForPregapQ16(dev, (uint)lba - 10, dbDev, out subBuf); for (retries = 0; retries < 10; retries++) { sense = supportsRwSubchannel ? GetSectorForPregapRaw(dev, (uint)lba, dbDev, out subBuf) : GetSectorForPregapQ16(dev, (uint)lba, dbDev, out subBuf); if (sense) { continue; } if (bcd == false) { BinaryToBcdQ(subBuf); } CRC16CCITTContext.Data(subBuf, 10, out crc); if (crc[0] == subBuf[10] && crc[1] == subBuf[11]) { break; } } if (retries == 10) { dumpLog?.WriteLine($"Could not get correct subchannel for sector {lba}"); updateStatus?.Invoke($"Could not get correct subchannel for sector {lba}"); } BcdToBinaryQ(subBuf); // If it's not Q position if ((subBuf[0] & 0xF) != 1) { // This means we already searched back, so search forward if (goFront) { lba++; if (lba == (int)previousTrack.TrackStartSector) { break; } continue; } // Search back goneBack = true; lba--; continue; } // Previous track if (subBuf[1] < track.TrackSequence) { lba++; // Already gone back, so go forward if (goneBack) { goFront = true; } continue; } // Same track, but not pregap if (subBuf[1] == track.TrackSequence && subBuf[2] > 0) { lba--; continue; } // Pregap according to Q position int pregapQ = (subBuf[3] * 60 * 75) + (subBuf[4] * 75) + subBuf[5] + 1; posQ = ((subBuf[7] * 60 * 75) + (subBuf[8] * 75) + subBuf[9]) - 150; int diff = posQ - lba; if (diff != 0) { inexactPositioning = true; } // Bigger than known change, otherwise we found it if (pregapQ > pregaps[track.TrackSequence]) { pregaps[track.TrackSequence] = pregapQ; } else if (pregapQ == pregaps[track.TrackSequence]) { break; } lba--; } } for (int i = 0; i < tracks.Length; i++) { tracks[i].TrackPregap = (ulong)pregaps[tracks[i].TrackSequence]; tracks[i].TrackStartSector -= tracks[i].TrackPregap; #if DEBUG dumpLog?.WriteLine($"Track {tracks[i].TrackSequence} pregap is {tracks[i].TrackPregap} sectors"); updateStatus?.Invoke($"Track {tracks[i].TrackSequence} pregap is {tracks[i].TrackPregap} sectors"); #endif } }