From d0c052c347068aa8e044fa5f7d63689ee61a8381 Mon Sep 17 00:00:00 2001 From: Natalia Portillo Date: Mon, 27 Apr 2020 02:29:49 +0100 Subject: [PATCH] Write subchannel log when dumping Compact Disc media. --- .idea/.idea.Aaru/.idea/contentModel.xml | 2 +- Aaru.Core/Aaru.Core.csproj | 1 + Aaru.Core/Devices/Dumping/CompactDisc/Data.cs | 4 +- Aaru.Core/Devices/Dumping/CompactDisc/Dump.cs | 16 +- .../Devices/Dumping/CompactDisc/Error.cs | 5 +- .../Devices/Dumping/CompactDisc/LeadOuts.cs | 12 +- Aaru.Core/Devices/Dumping/CompactDisc/Trim.cs | 9 +- Aaru.Core/Logging/SubchannelLog.cs | 423 ++++++++++++++++++ 8 files changed, 458 insertions(+), 14 deletions(-) create mode 100644 Aaru.Core/Logging/SubchannelLog.cs diff --git a/.idea/.idea.Aaru/.idea/contentModel.xml b/.idea/.idea.Aaru/.idea/contentModel.xml index 480f60bfd..d1e0a927a 100644 --- a/.idea/.idea.Aaru/.idea/contentModel.xml +++ b/.idea/.idea.Aaru/.idea/contentModel.xml @@ -311,6 +311,7 @@ + @@ -1369,7 +1370,6 @@ - diff --git a/Aaru.Core/Aaru.Core.csproj b/Aaru.Core/Aaru.Core.csproj index 171b8d21a..c18be8c8f 100644 --- a/Aaru.Core/Aaru.Core.csproj +++ b/Aaru.Core/Aaru.Core.csproj @@ -74,6 +74,7 @@ + diff --git a/Aaru.Core/Devices/Dumping/CompactDisc/Data.cs b/Aaru.Core/Devices/Dumping/CompactDisc/Data.cs index f14efeef4..60dd12454 100644 --- a/Aaru.Core/Devices/Dumping/CompactDisc/Data.cs +++ b/Aaru.Core/Devices/Dumping/CompactDisc/Data.cs @@ -83,7 +83,7 @@ namespace Aaru.Core.Devices.Dumping ref double minSpeed, out bool newTrim, bool nextData, int offsetBytes, bool read6, bool read10, bool read12, bool read16, bool readcd, int sectorsForOffset, uint subSize, MmcSubchannel supportedSubchannel, bool supportsLongSectors, ref double totalDuration, - Track[] tracks) + Track[] tracks, SubchannelLog subLog) { ulong sectorSpeedStart = 0; // Used to calculate correct speed DateTime timeSpeedStart = DateTime.UtcNow; // Time of start for speed calculation @@ -385,6 +385,7 @@ namespace Aaru.Core.Devices.Dumping _outputPlugin.WriteSectorsLong(data, i + r, 1); _outputPlugin.WriteSectorsTag(sub, i + r, 1, SectorTagType.CdSectorSubchannel); + subLog?.WriteEntry(sub, supportedSubchannel == MmcSubchannel.Raw, (long)(i + r), 1); } else { @@ -496,6 +497,7 @@ namespace Aaru.Core.Devices.Dumping _outputPlugin.WriteSectorsLong(data, i, blocksToRead); _outputPlugin.WriteSectorsTag(sub, i, blocksToRead, SectorTagType.CdSectorSubchannel); + subLog?.WriteEntry(sub, supportedSubchannel == MmcSubchannel.Raw, (long)i, blocksToRead); } else { diff --git a/Aaru.Core/Devices/Dumping/CompactDisc/Dump.cs b/Aaru.Core/Devices/Dumping/CompactDisc/Dump.cs index d92f1cdcd..62b805b94 100644 --- a/Aaru.Core/Devices/Dumping/CompactDisc/Dump.cs +++ b/Aaru.Core/Devices/Dumping/CompactDisc/Dump.cs @@ -97,6 +97,7 @@ namespace Aaru.Core.Devices.Dumping bool sense = true; // Sense indicator int sessions; // Number of sessions in disc DateTime start; // Start of operation + SubchannelLog subLog = null; // Subchannel log uint subSize; // Subchannel size in bytes TrackSubchannelType subType; // Track subchannel type bool supportsLongSectors = true; // Supports reading EDC and ECC @@ -787,6 +788,12 @@ namespace Aaru.Core.Devices.Dumping } } + if(supportedSubchannel != MmcSubchannel.None) + { + _dumpLog.WriteLine($"Creating subchannel log in {_outputPrefix + ".sub.log"}"); + subLog = new SubchannelLog(_outputPrefix + ".sub.log"); + } + // Set track flags foreach(KeyValuePair kvp in trackFlags) { @@ -1001,17 +1008,18 @@ namespace Aaru.Core.Devices.Dumping ref imageWriteDuration, lastSector, leadOutExtents, ref maxSpeed, mhddLog, ref minSpeed, out newTrim, tracks[0].TrackType != TrackType.Audio, offsetBytes, read6, read10, read12, read16, readcd, sectorsForOffset, subSize, supportedSubchannel, supportsLongSectors, ref totalDuration, - tracks); + tracks, subLog); // TODO: Enable when underlying images support lead-outs /* DumpCdLeadOuts(blocks, blockSize, ref currentSpeed, currentTry, extents, ibgLog, ref imageWriteDuration, leadOutExtents, ref maxSpeed, mhddLog, ref minSpeed, read6, read10, read12, read16, readcd, - supportedSubchannel, subSize, ref totalDuration); + supportedSubchannel, subSize, ref totalDuration, subLog); */ end = DateTime.UtcNow; mhddLog.Close(); + subLog?.Close(); ibgLog.Close(_dev, blocks, blockSize, (end - start).TotalSeconds, currentSpeed * 1024, (blockSize * (double)(blocks + 1)) / 1024 / (totalDuration / 1000), _devicePath); @@ -1034,10 +1042,10 @@ namespace Aaru.Core.Devices.Dumping TrimCdUserData(audioExtents, blockSize, currentTry, extents, newTrim, offsetBytes, read6, read10, read12, read16, readcd, sectorsForOffset, subSize, supportedSubchannel, supportsLongSectors, - ref totalDuration); + ref totalDuration, subLog); RetryCdUserData(audioExtents, blockSize, currentTry, extents, offsetBytes, readcd, sectorsForOffset, - subSize, supportedSubchannel, ref totalDuration); + subSize, supportedSubchannel, ref totalDuration, subLog); // Write media tags to image if(!_aborted) diff --git a/Aaru.Core/Devices/Dumping/CompactDisc/Error.cs b/Aaru.Core/Devices/Dumping/CompactDisc/Error.cs index c3c71ae9b..8fdb7fe38 100644 --- a/Aaru.Core/Devices/Dumping/CompactDisc/Error.cs +++ b/Aaru.Core/Devices/Dumping/CompactDisc/Error.cs @@ -36,6 +36,7 @@ using Aaru.CommonTypes.Enums; using Aaru.CommonTypes.Extents; using Aaru.CommonTypes.Structs.Devices.SCSI; using Aaru.Console; +using Aaru.Core.Logging; using Aaru.Decoders.SCSI; using Aaru.Devices; using Schemas; @@ -50,7 +51,7 @@ namespace Aaru.Core.Devices.Dumping { void RetryCdUserData(ExtentsULong audioExtents, uint blockSize, DumpHardwareType currentTry, ExtentsULong extents, int offsetBytes, bool readcd, int sectorsForOffset, uint subSize, - MmcSubchannel supportedSubchannel, ref double totalDuration) + MmcSubchannel supportedSubchannel, ref double totalDuration, SubchannelLog subLog) { bool sense = true; // Sense indicator byte[] cmdBuf = null; // Data buffer @@ -278,6 +279,7 @@ namespace Aaru.Core.Devices.Dumping Array.Copy(cmdBuf, sectorSize, sub, 0, subSize); _outputPlugin.WriteSectorLong(data, badSector); _outputPlugin.WriteSectorTag(sub, badSector, SectorTagType.CdSectorSubchannel); + subLog?.WriteEntry(sub, supportedSubchannel == MmcSubchannel.Raw, (long)badSector, 1); } else { @@ -375,6 +377,7 @@ namespace Aaru.Core.Devices.Dumping Array.Copy(cmdBuf, sectorSize, sub, 0, subSize); _outputPlugin.WriteSectorLong(data, badSector); _outputPlugin.WriteSectorTag(sub, badSector, SectorTagType.CdSectorSubchannel); + subLog?.WriteEntry(sub, supportedSubchannel == MmcSubchannel.Raw, (long)badSector, 1); } else { diff --git a/Aaru.Core/Devices/Dumping/CompactDisc/LeadOuts.cs b/Aaru.Core/Devices/Dumping/CompactDisc/LeadOuts.cs index 67888687b..a50c4acfc 100644 --- a/Aaru.Core/Devices/Dumping/CompactDisc/LeadOuts.cs +++ b/Aaru.Core/Devices/Dumping/CompactDisc/LeadOuts.cs @@ -69,7 +69,8 @@ namespace Aaru.Core.Devices.Dumping ExtentsULong extents, IbgLog ibgLog, ref double imageWriteDuration, ExtentsULong leadOutExtents, ref double maxSpeed, MhddLog mhddLog, ref double minSpeed, bool read6, bool read10, bool read12, bool read16, bool readcd, - MmcSubchannel supportedSubchannel, uint subSize, ref double totalDuration) + MmcSubchannel supportedSubchannel, uint subSize, ref double totalDuration, + SubchannelLog subLog) { byte[] cmdBuf = null; // Data buffer const uint sectorSize = 2352; // Full sector size @@ -152,6 +153,9 @@ namespace Aaru.Core.Devices.Dumping _outputPlugin.WriteSectorsLong(data, i, _maximumReadable); _outputPlugin.WriteSectorsTag(sub, i, _maximumReadable, SectorTagType.CdSectorSubchannel); + + subLog?.WriteEntry(sub, supportedSubchannel == MmcSubchannel.Raw, (long)i, + _maximumReadable); } else _outputPlugin.WriteSectors(cmdBuf, i, _maximumReadable); @@ -219,7 +223,8 @@ namespace Aaru.Core.Devices.Dumping ExtentsULong extents, IbgLog ibgLog, ref double imageWriteDuration, ExtentsULong leadOutExtents, ref double maxSpeed, MhddLog mhddLog, ref double minSpeed, bool read6, bool read10, bool read12, bool read16, bool readcd, - MmcSubchannel supportedSubchannel, uint subSize, ref double totalDuration) + MmcSubchannel supportedSubchannel, uint subSize, ref double totalDuration, + SubchannelLog subLog) { byte[] cmdBuf = null; // Data buffer const uint sectorSize = 2352; // Full sector size @@ -302,6 +307,9 @@ namespace Aaru.Core.Devices.Dumping _outputPlugin.WriteSectorsLong(data, i, _maximumReadable); _outputPlugin.WriteSectorsTag(sub, i, _maximumReadable, SectorTagType.CdSectorSubchannel); + + subLog?.WriteEntry(sub, supportedSubchannel == MmcSubchannel.Raw, (long)i, + _maximumReadable); } else _outputPlugin.WriteSectors(cmdBuf, i, _maximumReadable); diff --git a/Aaru.Core/Devices/Dumping/CompactDisc/Trim.cs b/Aaru.Core/Devices/Dumping/CompactDisc/Trim.cs index b5e7973a8..5bb7877e8 100644 --- a/Aaru.Core/Devices/Dumping/CompactDisc/Trim.cs +++ b/Aaru.Core/Devices/Dumping/CompactDisc/Trim.cs @@ -33,6 +33,7 @@ using System; using Aaru.CommonTypes.Enums; using Aaru.CommonTypes.Extents; +using Aaru.Core.Logging; using Aaru.Devices; using Schemas; @@ -47,7 +48,8 @@ namespace Aaru.Core.Devices.Dumping void TrimCdUserData(ExtentsULong audioExtents, uint blockSize, DumpHardwareType currentTry, ExtentsULong extents, bool newTrim, int offsetBytes, bool read6, bool read10, bool read12, bool read16, bool readcd, int sectorsForOffset, uint subSize, - MmcSubchannel supportedSubchannel, bool supportsLongSectors, ref double totalDuration) + MmcSubchannel supportedSubchannel, bool supportsLongSectors, ref double totalDuration, + SubchannelLog subLog) { DateTime start; DateTime end; @@ -177,13 +179,12 @@ namespace Aaru.Core.Devices.Dumping Array.Copy(cmdBuf, sectorSize, sub, 0, subSize); _outputPlugin.WriteSectorLong(data, badSector); _outputPlugin.WriteSectorTag(sub, badSector, SectorTagType.CdSectorSubchannel); + subLog?.WriteEntry(sub, supportedSubchannel == MmcSubchannel.Raw, (long)badSector, 1); } else { if(supportsLongSectors) - { _outputPlugin.WriteSectorLong(cmdBuf, badSector); - } else { if(cmdBuf.Length % sectorSize == 0) @@ -194,9 +195,7 @@ namespace Aaru.Core.Devices.Dumping _outputPlugin.WriteSector(data, badSector); } else - { _outputPlugin.WriteSectorLong(cmdBuf, badSector); - } } } } diff --git a/Aaru.Core/Logging/SubchannelLog.cs b/Aaru.Core/Logging/SubchannelLog.cs new file mode 100644 index 000000000..5e5c0236b --- /dev/null +++ b/Aaru.Core/Logging/SubchannelLog.cs @@ -0,0 +1,423 @@ +using System; +using System.IO; +using Aaru.Checksums; + +namespace Aaru.Core.Logging +{ + public class SubchannelLog + { + static readonly string[] _isrcTable = + { + // 0x00 + "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "", "", "", "", "", "", + + // 0x10 + "", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", + + // 0x20 + "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "", "", "", "", "", + + // 0x30 + "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "" + }; + readonly StreamWriter _logSw; + bool? _bcd; + + /// Initializes the dump log + /// Output log file + public SubchannelLog(string outputFile) + { + if(string.IsNullOrEmpty(outputFile)) + return; + + _logSw = new StreamWriter(outputFile, true); + + _logSw.WriteLine("Start subchannel logging at {0}", DateTime.Now); + _logSw.WriteLine("######################################################"); + _logSw.Flush(); + } + + /// Finishes and closes the dump log + public void Close() + { + _logSw.WriteLine("######################################################"); + _logSw.WriteLine("End logging at {0}", DateTime.Now); + _logSw.Close(); + } + + public void WriteEntry(byte[] subchannel, bool raw, long startingLba, uint blocks) + { + int subSize = raw ? 96 : 16; + + if(subchannel.Length / subSize != blocks) + { + _logSw.WriteLine("Data length is invalid!"); + _logSw.Flush(); + + return; + } + + int[] p = new int[subchannel.Length / 8]; + int[] q = new int[subchannel.Length / 8]; + int[] r = new int[subchannel.Length / 8]; + int[] s = new int[subchannel.Length / 8]; + int[] t = new int[subchannel.Length / 8]; + int[] u = new int[subchannel.Length / 8]; + int[] v = new int[subchannel.Length / 8]; + int[] w = new int[subchannel.Length / 8]; + + if(raw) + { + for(int i = 0; i < subchannel.Length; i += 8) + { + p[i / 8] = subchannel[i] & 0x80; + p[i / 8] += (subchannel[i + 1] & 0x80) >> 1; + p[i / 8] += (subchannel[i + 2] & 0x80) >> 2; + p[i / 8] += (subchannel[i + 3] & 0x80) >> 3; + p[i / 8] += (subchannel[i + 4] & 0x80) >> 4; + p[i / 8] += (subchannel[i + 5] & 0x80) >> 5; + p[i / 8] += (subchannel[i + 6] & 0x80) >> 6; + p[i / 8] += (subchannel[i + 7] & 0x80) >> 7; + + q[i / 8] = (subchannel[i] & 0x40) << 1; + q[i / 8] += subchannel[i + 1] & 0x40; + q[i / 8] += (subchannel[i + 2] & 0x40) >> 1; + q[i / 8] += (subchannel[i + 3] & 0x40) >> 2; + q[i / 8] += (subchannel[i + 4] & 0x40) >> 3; + q[i / 8] += (subchannel[i + 5] & 0x40) >> 4; + q[i / 8] += (subchannel[i + 6] & 0x40) >> 5; + q[i / 8] += (subchannel[i + 7] & 0x40) >> 6; + + r[i / 8] = (subchannel[i] & 0x20) << 2; + r[i / 8] += (subchannel[i + 1] & 0x20) << 1; + r[i / 8] += subchannel[i + 2] & 0x20; + r[i / 8] += (subchannel[i + 3] & 0x20) >> 1; + r[i / 8] += (subchannel[i + 4] & 0x20) >> 2; + r[i / 8] += (subchannel[i + 5] & 0x20) >> 3; + r[i / 8] += (subchannel[i + 6] & 0x20) >> 4; + r[i / 8] += (subchannel[i + 7] & 0x20) >> 5; + + s[i / 8] = (subchannel[i] & 0x10) << 3; + s[i / 8] += (subchannel[i + 1] & 0x10) << 2; + s[i / 8] += (subchannel[i + 2] & 0x10) << 1; + s[i / 8] += subchannel[i + 3] & 0x10; + s[i / 8] += (subchannel[i + 4] & 0x10) >> 1; + s[i / 8] += (subchannel[i + 5] & 0x10) >> 2; + s[i / 8] += (subchannel[i + 6] & 0x10) >> 3; + s[i / 8] += (subchannel[i + 7] & 0x10) >> 4; + + t[i / 8] = (subchannel[i] & 0x08) << 4; + t[i / 8] += (subchannel[i + 1] & 0x08) << 3; + t[i / 8] += (subchannel[i + 2] & 0x08) << 2; + t[i / 8] += (subchannel[i + 3] & 0x08) << 1; + t[i / 8] += subchannel[i + 4] & 0x08; + t[i / 8] += (subchannel[i + 5] & 0x08) >> 1; + t[i / 8] += (subchannel[i + 6] & 0x08) >> 2; + t[i / 8] += (subchannel[i + 7] & 0x08) >> 3; + + u[i / 8] = (subchannel[i] & 0x04) << 5; + u[i / 8] += (subchannel[i + 1] & 0x04) << 4; + u[i / 8] += (subchannel[i + 2] & 0x04) << 3; + u[i / 8] += (subchannel[i + 3] & 0x04) << 2; + u[i / 8] += (subchannel[i + 4] & 0x04) << 1; + u[i / 8] += subchannel[i + 5] & 0x04; + u[i / 8] += (subchannel[i + 6] & 0x04) >> 1; + u[i / 8] += (subchannel[i + 7] & 0x04) >> 2; + + v[i / 8] = (subchannel[i] & 0x02) << 6; + v[i / 8] += (subchannel[i + 1] & 0x02) << 5; + v[i / 8] += (subchannel[i + 2] & 0x02) << 4; + v[i / 8] += (subchannel[i + 3] & 0x02) << 3; + v[i / 8] += (subchannel[i + 4] & 0x02) << 2; + v[i / 8] += (subchannel[i + 5] & 0x02) << 1; + v[i / 8] += subchannel[i + 6] & 0x02; + v[i / 8] += (subchannel[i + 7] & 0x02) >> 1; + + w[i / 8] = (subchannel[i] & 0x01) << 7; + w[i / 8] += (subchannel[i + 1] & 0x01) << 6; + w[i / 8] += (subchannel[i + 2] & 0x01) << 5; + w[i / 8] += (subchannel[i + 3] & 0x01) << 4; + w[i / 8] += (subchannel[i + 4] & 0x01) << 3; + w[i / 8] += (subchannel[i + 5] & 0x01) << 2; + w[i / 8] += (subchannel[i + 6] & 0x01) << 1; + w[i / 8] += subchannel[i + 7] & 0x01; + } + } + else + { + r = null; + s = null; + t = null; + u = null; + v = null; + w = null; + int pPos = 0; + int qPos = 0; + + for(int i = 0; i < subchannel.Length; i += 16) + { + q[qPos++] = subchannel[i + 0]; + q[qPos++] = subchannel[i + 1]; + q[qPos++] = subchannel[i + 2]; + q[qPos++] = subchannel[i + 3]; + q[qPos++] = subchannel[i + 4]; + q[qPos++] = subchannel[i + 5]; + q[qPos++] = subchannel[i + 6]; + q[qPos++] = subchannel[i + 7]; + q[qPos++] = subchannel[i + 8]; + q[qPos++] = subchannel[i + 9]; + q[qPos++] = subchannel[i + 10]; + q[qPos++] = subchannel[i + 11]; + + if((subchannel[i + 15] & 0x80) <= 0) + continue; + + p[pPos++] = 0xFF; + p[pPos++] = 0xFF; + p[pPos++] = 0xFF; + p[pPos++] = 0xFF; + p[pPos++] = 0xFF; + p[pPos++] = 0xFF; + p[pPos++] = 0xFF; + p[pPos++] = 0xFF; + p[pPos++] = 0xFF; + p[pPos++] = 0xFF; + p[pPos++] = 0xFF; + p[pPos++] = 0xFF; + } + } + + if(_bcd is null) + { + _bcd = (q[9] & 0x10) > 0; + + _logSw.WriteLine(_bcd switch + { + true => "Subchannel is BCD", + false => "Subchannel is not BCD" + }); + } + + for(uint block = 0; block < blocks; block++) + { + bool rwEmpty = true; + + if(r != null) + { + for(uint i = 12 * block; i < (12 * block) + 12; i++) + { + if(r[i] == 0 && + s[i] == 0 && + t[i] == 0 && + u[i] == 0 && + v[i] == 0 && + w[i] == 0) + continue; + + rwEmpty = false; + + break; + } + } + + bool corruptedPause = false; + bool pause = false; + + for(int i = 0; i < 12; i++) + { + if(p[i] == 0 || + p[i] == 0xFF) + continue; + + corruptedPause = true; + + break; + } + + if(!corruptedPause) + pause = p[0] == 1; + + byte[] subBuf = new byte[12]; + subBuf[0] = (byte)q[0 + (block * 12)]; + subBuf[1] = (byte)q[1 + (block * 12)]; + subBuf[2] = (byte)q[2 + (block * 12)]; + subBuf[3] = (byte)q[3 + (block * 12)]; + subBuf[4] = (byte)q[4 + (block * 12)]; + subBuf[5] = (byte)q[5 + (block * 12)]; + subBuf[6] = (byte)q[6 + (block * 12)]; + subBuf[7] = (byte)q[7 + (block * 12)]; + subBuf[8] = (byte)q[8 + (block * 12)]; + subBuf[9] = (byte)q[9 + (block * 12)]; + subBuf[10] = (byte)q[10 + (block * 12)]; + subBuf[11] = (byte)q[11 + (block * 12)]; + + _logSw.WriteLine(PrettifyQ(subBuf, _bcd == true, startingLba + block, corruptedPause, pause, rwEmpty)); + } + + _logSw.Flush(); + } + + static void BinaryToBcdQ(byte[] q) + { + q[1] = (byte)(((q[1] / 10) << 4) + (q[1] % 10)); + q[2] = (byte)(((q[2] / 10) << 4) + (q[2] % 10)); + q[3] = (byte)(((q[3] / 10) << 4) + (q[3] % 10)); + q[4] = (byte)(((q[4] / 10) << 4) + (q[4] % 10)); + q[5] = (byte)(((q[5] / 10) << 4) + (q[5] % 10)); + q[6] = (byte)(((q[6] / 10) << 4) + (q[6] % 10)); + q[7] = (byte)(((q[7] / 10) << 4) + (q[7] % 10)); + q[8] = (byte)(((q[8] / 10) << 4) + (q[8] % 10)); + q[9] = (byte)(((q[9] / 10) << 4) + (q[9] % 10)); + } + + static void BcdToBinaryQ(byte[] q) + { + q[1] = (byte)(((q[1] / 16) * 10) + (q[1] & 0x0F)); + q[2] = (byte)(((q[2] / 16) * 10) + (q[2] & 0x0F)); + q[3] = (byte)(((q[3] / 16) * 10) + (q[3] & 0x0F)); + q[4] = (byte)(((q[4] / 16) * 10) + (q[4] & 0x0F)); + q[5] = (byte)(((q[5] / 16) * 10) + (q[5] & 0x0F)); + q[6] = (byte)(((q[6] / 16) * 10) + (q[6] & 0x0F)); + q[7] = (byte)(((q[7] / 16) * 10) + (q[7] & 0x0F)); + q[8] = (byte)(((q[8] / 16) * 10) + (q[8] & 0x0F)); + q[9] = (byte)(((q[9] / 16) * 10) + (q[9] & 0x0F)); + } + + static string PrettifyQ(byte[] subBuf, bool bcd, long lba, bool corruptedPause, bool pause, bool rwEmpty) + { + CRC16CCITTContext.Data(subBuf, 10, out byte[] crc); + + bool crcOk = crc[0] == subBuf[10] && crc[1] == subBuf[11]; + long minute = (lba + 150) / 4500; + long second = ((lba + 150) % 4500) / 75; + long frame = (lba + 150) % 4500 % 75; + string area; + int control = (subBuf[0] & 0xF0) / 16; + int adr = subBuf[0] & 0x0F; + + string controlInfo = ((control & 0xC) / 4) switch + { + 0 => $"stereo audio {((control & 0x01) == 1 ? "with" : "without")} pre-emphasis", + 1 => $"{((control & 0x01) == 1 ? "incremental" : "uninterrupted")} data", + 2 => $"quadraphonic audio {((control & 0x01) == 1 ? "with" : "without")} pre-emphasis", + _ => $"reserved control value {control & 0x01}" + }; + + string copy = (control & 0x02) > 0 ? "copy permitted" : "copy prohibited"; + + if(bcd) + BcdToBinaryQ(subBuf); + + int qPos = ((subBuf[3] * 60 * 75) + (subBuf[4] * 75) + subBuf[5]) - 150; + byte pmin = subBuf[7]; + byte psec = subBuf[8]; + + int qStart = ((subBuf[7] * 60 * 75) + (subBuf[8] * 75) + subBuf[9]) - 150; + int nextPos = ((subBuf[3] * 60 * 75) + (subBuf[4] * 75) + subBuf[5]) - 150; + byte zero = subBuf[6]; + int maxOut = ((subBuf[7] * 60 * 75) + (subBuf[8] * 75) + subBuf[9]) - 150; + bool final = subBuf[3] == 0xFF && subBuf[4] == 0xFF && subBuf[5] == 0xFF; + + BinaryToBcdQ(subBuf); + + if(lba < 0) + { + area = "Lead-In"; + + switch(adr) + { + case 1 when subBuf[2] < 0xA0: + return + $"{minute:D2}:{second:D2}:{frame:D2} - LBA {lba,6}: {area} area, {(corruptedPause ? "corrupted pause" : pause ? "pause" : "not pause")}, {controlInfo}, {copy}, Q mode {adr} position: {subBuf[3]:X2}:{subBuf[4]:X2}:{subBuf[5]:X2} (LBA {qPos}), track {subBuf[2]:X} starts at {subBuf[7]:X2}:{subBuf[8]:X2}:{subBuf[9]:X2} (LBA {qStart}), Q CRC 0x{subBuf[10]:X2}{subBuf[11]:X2} ({(crcOk ? "OK" : "BAD")}), R-W {(rwEmpty ? "empty" : "not empty")}"; + case 1 when subBuf[2] == 0xA0: + { + string format = subBuf[8] switch + { + 0x00 => "CD-DA / CD-ROM", + 0x10 => "CD-i", + 0x20 => "CD-ROM XA", + _ => $"unknown {subBuf[0]:X2}" + }; + + return + $"{minute:D2}:{second:D2}:{frame:D2} - LBA {lba,6}: {area} area, {(corruptedPause ? "corrupted pause" : pause ? "pause" : "not pause")}, {controlInfo}, {copy}, Q mode {adr} position: {subBuf[3]:X2}:{subBuf[4]:X2}:{subBuf[5]:X2} (LBA {qPos}), track {subBuf[2]:X} is first program area track in {format} format, Q CRC 0x{subBuf[10]:X2}{subBuf[11]:X2} ({(crcOk ? "OK" : "BAD")}), R-W {(rwEmpty ? "empty" : "not empty")}"; + } + case 1 when subBuf[2] == 0xA1: + return + $"{minute:D2}:{second:D2}:{frame:D2} - LBA {lba,6}: {area} area, {(corruptedPause ? "corrupted pause" : pause ? "pause" : "not pause")}, {controlInfo}, {copy}, Q mode {adr} position: {subBuf[3]:X2}:{subBuf[4]:X2}:{subBuf[5]:X2} (LBA {qPos}), track {subBuf[2]:X} is last program area track, Q CRC 0x{subBuf[10]:X2}{subBuf[11]:X2} ({(crcOk ? "OK" : "BAD")}), R-W {(rwEmpty ? "empty" : "not empty")}"; + case 1: + return subBuf[2] == 0xA2 + ? $"{minute:D2}:{second:D2}:{frame:D2} - LBA {lba,6}: {area} area, {(corruptedPause ? "corrupted pause" : pause ? "pause" : "not pause")}, {controlInfo}, {copy}, Q mode {adr} position: {subBuf[3]:X2}:{subBuf[4]:X2}:{subBuf[5]:X2} (LBA {qPos}), track {subBuf[2]:X} starts at {subBuf[7]:X2}{subBuf[8]:X2}{subBuf[9]:X2} (LBA {qStart}), Q CRC 0x{subBuf[10]:X2}{subBuf[11]:X2} ({(crcOk ? "OK" : "BAD")}), R-W {(rwEmpty ? "empty" : "not empty")}" + : $"{minute:D2}:{second:D2}:{frame:D2} - LBA {lba,6}: {area} area, {(corruptedPause ? "corrupted pause" : pause ? "pause" : "not pause")}, {controlInfo}, {copy}, Q: {subBuf[0]:X2} {subBuf[1]:X2} {subBuf[2]:X2} {subBuf[3]:X2} {subBuf[4]:X2} {subBuf[5]:X2} {subBuf[6]:X2} {subBuf[7]:X2} {subBuf[8]:X2} {subBuf[9]:X2} CRC 0x{subBuf[10]:X2}{subBuf[11]:X2} ({(crcOk ? "OK" : "BAD")}), R-W {(rwEmpty ? "empty" : "not empty")}"; + case 2: + return + $"{minute:D2}:{second:D2}:{frame:D2} - LBA {lba,6}: {area} area, {(corruptedPause ? "corrupted pause" : pause ? "pause" : "not pause")}, {controlInfo}, {copy}, Q mode {adr} MCN: {subBuf[1]:X2}{subBuf[2]:X2}{subBuf[3]:X2}{subBuf[4]:X2}{subBuf[5]:X2}{subBuf[6]:X2}{subBuf[7] / 8:X} frame {subBuf[9]:X2} CRC 0x{subBuf[10]:X2}{subBuf[11]:X2} ({(crcOk ? "OK" : "BAD")}), R-W {(rwEmpty ? "empty" : "not empty")}"; + } + + if(adr != 5) + return + $"{minute:D2}:{second:D2}:{frame:D2} - LBA {lba,6}: {area} area, {(corruptedPause ? "corrupted pause" : pause ? "pause" : "not pause")}, {controlInfo}, {copy}, Q: {subBuf[0]:X2} {subBuf[1]:X2} {subBuf[2]:X2} {subBuf[3]:X2} {subBuf[4]:X2} {subBuf[5]:X2} {subBuf[6]:X2} {subBuf[7]:X2} {subBuf[8]:X2} {subBuf[9]:X2} CRC 0x{subBuf[10]:X2}{subBuf[11]:X2} ({(crcOk ? "OK" : "BAD")}), R-W {(rwEmpty ? "empty" : "not empty")}"; + + if(subBuf[2] <= 0x40) + { + return + $"{minute:D2}:{second:D2}:{frame:D2} - LBA {lba,6}: {area} area, {(corruptedPause ? "corrupted pause" : pause ? "pause" : "not pause")}, {controlInfo}, {copy}, Q mode {adr} skip interval start time {subBuf[7]:X2}{subBuf[8]:X2}{subBuf[9]:X2}, skip interval stop time {subBuf[3]:X2}{subBuf[4]:X2}{subBuf[5]:X2}, CRC 0x{subBuf[10]:X2}{subBuf[11]:X2} ({(crcOk ? "OK" : "BAD")}), R-W {(rwEmpty ? "empty" : "not empty")}"; + } + + if(subBuf[2] == 0xB0) + { + return final + ? $"{minute:D2}:{second:D2}:{frame:D2} - LBA {lba,6}: {area} area, {(corruptedPause ? "corrupted pause" : pause ? "pause" : "not pause")}, {controlInfo}, {copy}, Q mode {adr} next program area can start at {subBuf[3]:X2}:{subBuf[4]:X2}:{subBuf[5]:X2} (LBA {nextPos}), last-session, {zero} mode 5 pointers, CRC 0x{subBuf[10]:X2}{subBuf[11]:X2} ({(crcOk ? "OK" : "BAD")}), R-W {(rwEmpty ? "empty" : "not empty")}" + : $"{minute:D2}:{second:D2}:{frame:D2} - LBA {lba,6}: {area} area, {(corruptedPause ? "corrupted pause" : pause ? "pause" : "not pause")}, {controlInfo}, {copy}, Q mode {adr} next program area can start at {subBuf[3]:X2}:{subBuf[4]:X2}:{subBuf[5]:X2} (LBA {nextPos}), maximum Lead-out at {subBuf[7]:X2}:{subBuf[8]:X2}:{subBuf[9]:X2} (LBA {maxOut}), {zero} mode 5 pointers, CRC 0x{subBuf[10]:X2}{subBuf[11]:X2} ({(crcOk ? "OK" : "BAD")}), R-W {(rwEmpty ? "empty" : "not empty")}"; + } + + if(subBuf[2] == 0xB1) + { + return + $"{minute:D2}:{second:D2}:{frame:D2} - LBA {lba,6}: {area} area, {(corruptedPause ? "corrupted pause" : pause ? "pause" : "not pause")}, {controlInfo}, {copy}, Q mode {adr}, {pmin} skip interval pointers, {psec} skip track assignments, CRC 0x{subBuf[10]:X2}{subBuf[11]:X2} ({(crcOk ? "OK" : "BAD")}), R-W {(rwEmpty ? "empty" : "not empty")}"; + } + + if(subBuf[2] != 0xB2 && + subBuf[2] != 0xB3 && + subBuf[2] != 0xB4) + return subBuf[2] == 0xC0 + ? $"{minute:D2}:{second:D2}:{frame:D2} - LBA {lba,6}: {area} area, {(corruptedPause ? "corrupted pause" : pause ? "pause" : "not pause")}, {controlInfo}, {copy}, Q mode {adr}, ATIP values {subBuf[3]:X2}, {subBuf[4]:X2}, {subBuf[5]:X2}, first disc Lead-in starts at {subBuf[7]:X2}{subBuf[8]:X2}{subBuf[9]:X2} (LBA {qStart}), CRC 0x{subBuf[10]:X2}{subBuf[11]:X2} ({(crcOk ? "OK" : "BAD")}), R-W {(rwEmpty ? "empty" : "not empty")}" + : $"{minute:D2}:{second:D2}:{frame:D2} - LBA {lba,6}: {area} area, {(corruptedPause ? "corrupted pause" : pause ? "pause" : "not pause")}, {controlInfo}, {copy}, Q: {subBuf[0]:X2} {subBuf[1]:X2} {subBuf[2]:X2} {subBuf[3]:X2} {subBuf[4]:X2} {subBuf[5]:X2} {subBuf[6]:X2} {subBuf[7]:X2} {subBuf[8]:X2} {subBuf[9]:X2} CRC 0x{subBuf[10]:X2}{subBuf[11]:X2} ({(crcOk ? "OK" : "BAD")}), R-W {(rwEmpty ? "empty" : "not empty")}"; + + string skipTracks = $"{subBuf[3]:X2}"; + + if(subBuf[4] > 0) + skipTracks += $", {subBuf[4]:X2}"; + + if(subBuf[5] > 0) + skipTracks += $", {subBuf[4]:X2}"; + + if(subBuf[7] > 0) + skipTracks += $", {subBuf[4]:X2}"; + + if(subBuf[8] > 0) + skipTracks += $", {subBuf[4]:X2}"; + + if(subBuf[9] > 0) + skipTracks += $", {subBuf[4]:X2}"; + + return + $"{minute:D2}:{second:D2}:{frame:D2} - LBA {lba,6}: {area} area, {(corruptedPause ? "corrupted pause" : pause ? "pause" : "not pause")}, {controlInfo}, {copy}, Q mode {adr}, tracks {skipTracks} to be skipped, CRC 0x{subBuf[10]:X2}{subBuf[11]:X2} ({(crcOk ? "OK" : "BAD")}), R-W {(rwEmpty ? "empty" : "not empty")}"; + } + + area = subBuf[1] == 0xAA ? "Lead-out" : "Program"; + + return adr switch + { + 1 => + $"{minute:D2}:{second:D2}:{frame:D2} - LBA {lba,6}: {area} area, {(corruptedPause ? "corrupted pause" : pause ? "pause" : "not pause")}, {controlInfo}, {copy}, Q mode {adr} position: track {subBuf[1]:X} index {subBuf[2]:X} relative position {subBuf[3]:X2}:{subBuf[4]:X2}:{subBuf[5]:X2} (LBA {qPos + 150}), absolute position {subBuf[7]:X2}:{subBuf[8]:X2}:{subBuf[9]:X2} (LBA {qStart}), Q CRC 0x{subBuf[10]:X2}{subBuf[11]:X2} ({(crcOk ? "OK" : "BAD")}), R-W {(rwEmpty ? "empty" : "not empty")}", + 2 => + $"{minute:D2}:{second:D2}:{frame:D2} - LBA {lba,6}: {area} area, {(corruptedPause ? "corrupted pause" : pause ? "pause" : "not pause")}, {controlInfo}, {copy}, Q mode {adr} MCN: {subBuf[1]:X2}{subBuf[2]:X2}{subBuf[3]:X2}{subBuf[4]:X2}{subBuf[5]:X2}{subBuf[6]:X2}{subBuf[7] / 8:X} frame {subBuf[9]:X2} CRC 0x{subBuf[10]:X2}{subBuf[11]:X2} ({(crcOk ? "OK" : "BAD")}), R-W {(rwEmpty ? "empty" : "not empty")}", + 3 => + $"{minute:D2}:{second:D2}:{frame:D2} - LBA {lba,6}: {area} area, {(corruptedPause ? "corrupted pause" : pause ? "pause" : "not pause")}, {controlInfo}, {copy}, Q mode {adr} ISRC: {_isrcTable[subBuf[1] / 4]}{_isrcTable[((subBuf[1] & 3) * 16) + (subBuf[2] / 16)]}{_isrcTable[((subBuf[2] & 0xF) * 4) + (subBuf[3] / 64)]}{_isrcTable[subBuf[3] & 0x3F]}{_isrcTable[subBuf[4] / 4]}{subBuf[5]:X2}{subBuf[6]:X2}{subBuf[7]:X2}{subBuf[8] / 16:X2} frame {subBuf[9]:X2} CRC 0x{subBuf[10]:X2}{subBuf[11]:X2} ({(crcOk ? "OK" : "BAD")}), R-W {(rwEmpty ? "empty" : "not empty")}", + _ => + $"{minute:D2}:{second:D2}:{frame:D2} - LBA {lba,6}: {area} area, {(corruptedPause ? "corrupted pause" : pause ? "pause" : "not pause")}, {controlInfo}, {copy}, Q: {subBuf[0]:X2} {subBuf[1]:X2} {subBuf[2]:X2} {subBuf[3]:X2} {subBuf[4]:X2} {subBuf[5]:X2} {subBuf[6]:X2} {subBuf[7]:X2} {subBuf[8]:X2} {subBuf[9]:X2} CRC 0x{subBuf[10]:X2}{subBuf[11]:X2} ({(crcOk ? "OK" : "BAD")}), R-W {(rwEmpty ? "empty" : "not empty")}" + }; + } + } +} \ No newline at end of file