diff --git a/.idea/.idea.Aaru/.idea/contentModel.xml b/.idea/.idea.Aaru/.idea/contentModel.xml
index 43e204c2b..08ca96ec2 100644
--- a/.idea/.idea.Aaru/.idea/contentModel.xml
+++ b/.idea/.idea.Aaru/.idea/contentModel.xml
@@ -2196,6 +2196,7 @@
+
diff --git a/Aaru.Tests.Devices/Aaru.Tests.Devices.csproj b/Aaru.Tests.Devices/Aaru.Tests.Devices.csproj
index 9c6931c18..ae3ce601a 100644
--- a/Aaru.Tests.Devices/Aaru.Tests.Devices.csproj
+++ b/Aaru.Tests.Devices/Aaru.Tests.Devices.csproj
@@ -54,6 +54,7 @@
+
diff --git a/Aaru.Tests.Devices/SCSI_MMC/LeadOutTrap.cs b/Aaru.Tests.Devices/SCSI_MMC/LeadOutTrap.cs
new file mode 100644
index 000000000..0b5ef0626
--- /dev/null
+++ b/Aaru.Tests.Devices/SCSI_MMC/LeadOutTrap.cs
@@ -0,0 +1,539 @@
+using System.Linq;
+using System.Threading;
+using Aaru.Console;
+using Aaru.Decoders.CD;
+using Aaru.Decoders.SCSI;
+using Aaru.Devices;
+
+namespace Aaru.Tests.Devices
+{
+ internal static partial class ScsiMmc
+ {
+ static void ReadLeadOutUsingTrapDisc(string devPath, Device dev)
+ {
+ string strDev;
+ int item;
+ bool tocIsNotBcd = false;
+
+ parameters:
+
+ while(true)
+ {
+ System.Console.Clear();
+ AaruConsole.WriteLine("Device: {0}", devPath);
+ AaruConsole.WriteLine();
+ AaruConsole.WriteLine("Choose what to do:");
+ AaruConsole.WriteLine("1.- Try to read Lead-Out using a trap disc.");
+ AaruConsole.WriteLine("0.- Return to special SCSI MultiMedia Commands menu.");
+
+ strDev = System.Console.ReadLine();
+
+ if(!int.TryParse(strDev, out item))
+ {
+ AaruConsole.WriteLine("Not a number. Press any key to continue...");
+ System.Console.ReadKey();
+
+ continue;
+ }
+
+ switch(item)
+ {
+ case 0:
+ AaruConsole.WriteLine("Returning to special SCSI MultiMedia Commands menu...");
+
+ return;
+ case 1: goto start;
+ }
+ }
+
+ start:
+ System.Console.Clear();
+
+ AaruConsole.WriteLine("Ejecting disc...");
+
+ dev.AllowMediumRemoval(out _, dev.Timeout, out _);
+ dev.EjectTray(out _, dev.Timeout, out _);
+
+ AaruConsole.WriteLine("Please insert a data only disc inside...");
+ AaruConsole.WriteLine("Press any key to continue...");
+ System.Console.ReadLine();
+
+ AaruConsole.WriteLine("Waiting 5 seconds...");
+ Thread.Sleep(5000);
+
+ AaruConsole.WriteLine("Sending READ FULL TOC to the device...");
+
+ dev.ScsiTestUnitReady(out _, dev.Timeout, out _);
+
+ bool sense = dev.ReadRawToc(out byte[] buffer, out byte[] senseBuffer, 1, dev.Timeout, out _);
+
+ if(sense)
+ {
+ AaruConsole.WriteLine("READ FULL TOC failed...");
+ AaruConsole.WriteLine("Press any key to continue...");
+ System.Console.ReadLine();
+
+ goto parameters;
+ }
+
+ FullTOC.CDFullTOC? decodedToc = FullTOC.Decode(buffer);
+
+ if(decodedToc is null)
+ {
+ AaruConsole.WriteLine("Could not decode TOC...");
+ AaruConsole.WriteLine("Press any key to continue...");
+ System.Console.ReadLine();
+
+ goto parameters;
+ }
+
+ FullTOC.CDFullTOC toc = decodedToc.Value;
+
+ FullTOC.TrackDataDescriptor leadOutTrack = toc.TrackDescriptors.FirstOrDefault(t => t.POINT == 0xA2);
+
+ if(leadOutTrack.POINT != 0xA2)
+ {
+ AaruConsole.WriteLine("Cannot find lead-out...");
+ AaruConsole.WriteLine("Press any key to continue...");
+ System.Console.ReadLine();
+
+ goto parameters;
+ }
+
+ int min = ((leadOutTrack.PMIN >> 4) * 10) + (leadOutTrack.PMIN & 0x0F);
+ int sec = ((leadOutTrack.PSEC >> 4) * 10) + (leadOutTrack.PSEC & 0x0F);
+ int frame = ((leadOutTrack.PFRAME >> 4) * 10) + (leadOutTrack.PFRAME & 0x0F);
+
+ int sectors = ((min * 60 * 75) + (sec * 75) + frame) - 150;
+
+ AaruConsole.WriteLine("Data disc shows {0} sectors...", sectors);
+
+ AaruConsole.WriteLine("Ejecting disc...");
+
+ dev.AllowMediumRemoval(out _, dev.Timeout, out _);
+ dev.EjectTray(out _, dev.Timeout, out _);
+
+ AaruConsole.WriteLine("Please insert the trap disc inside...");
+ AaruConsole.WriteLine("Press any key to continue...");
+ System.Console.ReadLine();
+
+ AaruConsole.WriteLine("Waiting 5 seconds...");
+ Thread.Sleep(5000);
+
+ AaruConsole.WriteLine("Sending READ FULL TOC to the device...");
+
+ dev.ScsiTestUnitReady(out _, dev.Timeout, out _);
+ sense = dev.ReadRawToc(out buffer, out senseBuffer, 1, dev.Timeout, out _);
+
+ if(sense)
+ {
+ AaruConsole.WriteLine("READ FULL TOC failed...");
+ AaruConsole.WriteLine("Press any key to continue...");
+ System.Console.ReadLine();
+
+ goto parameters;
+ }
+
+ decodedToc = FullTOC.Decode(buffer);
+
+ if(decodedToc is null)
+ {
+ AaruConsole.WriteLine("Could not decode TOC...");
+ AaruConsole.WriteLine("Press any key to continue...");
+ System.Console.ReadLine();
+
+ goto parameters;
+ }
+
+ toc = decodedToc.Value;
+
+ leadOutTrack = toc.TrackDescriptors.FirstOrDefault(t => t.POINT == 0xA2);
+
+ if(leadOutTrack.POINT != 0xA2)
+ {
+ AaruConsole.WriteLine("Cannot find lead-out...");
+ AaruConsole.WriteLine("Press any key to continue...");
+ System.Console.ReadLine();
+
+ goto parameters;
+ }
+
+ min = 0;
+
+ if(leadOutTrack.PMIN == 122)
+ tocIsNotBcd = true;
+
+ if(leadOutTrack.PMIN >= 0xA0 &&
+ !tocIsNotBcd)
+ {
+ min += 90;
+ leadOutTrack.PMIN -= 0x90;
+ }
+
+ if(tocIsNotBcd)
+ {
+ min = leadOutTrack.PMIN;
+ sec = leadOutTrack.PSEC;
+ frame = leadOutTrack.PFRAME;
+ }
+ else
+ {
+ min += ((leadOutTrack.PMIN >> 4) * 10) + (leadOutTrack.PMIN & 0x0F);
+ sec = ((leadOutTrack.PSEC >> 4) * 10) + (leadOutTrack.PSEC & 0x0F);
+ frame = ((leadOutTrack.PFRAME >> 4) * 10) + (leadOutTrack.PFRAME & 0x0F);
+ }
+
+ int trapSectors = ((min * 60 * 75) + (sec * 75) + frame) - 150;
+
+ AaruConsole.WriteLine("Trap disc shows {0} sectors...", trapSectors);
+
+ if(trapSectors < sectors + 100)
+ {
+ AaruConsole.WriteLine("Trap disc doesn't have enough sectors...");
+ AaruConsole.WriteLine("Press any key to continue...");
+ System.Console.ReadLine();
+
+ goto parameters;
+ }
+
+ AaruConsole.WriteLine("Stopping motor...");
+
+ dev.StopUnit(out _, dev.Timeout, out _);
+
+ AaruConsole.WriteLine("Please MANUALLY get the trap disc out and put the data disc back inside...");
+ AaruConsole.WriteLine("Press any key to continue...");
+ System.Console.ReadLine();
+
+ AaruConsole.WriteLine("Waiting 5 seconds...");
+ Thread.Sleep(5000);
+
+ AaruConsole.WriteLine("Sending READ FULL TOC to the device...");
+
+ sense = dev.ReadRawToc(out buffer, out senseBuffer, 1, dev.Timeout, out _);
+
+ // Just try again to clear any "disc changed" events
+ if(sense)
+ sense = dev.ReadRawToc(out buffer, out senseBuffer, 1, dev.Timeout, out _);
+
+ if(sense)
+ {
+ AaruConsole.WriteLine("READ FULL TOC failed...");
+ AaruConsole.WriteLine("Press any key to continue...");
+ System.Console.ReadLine();
+
+ goto parameters;
+ }
+
+ decodedToc = FullTOC.Decode(buffer);
+
+ if(decodedToc is null)
+ {
+ AaruConsole.WriteLine("Could not decode TOC...");
+ AaruConsole.WriteLine("Press any key to continue...");
+ System.Console.ReadLine();
+
+ goto parameters;
+ }
+
+ toc = decodedToc.Value;
+
+ FullTOC.TrackDataDescriptor newLeadOutTrack = toc.TrackDescriptors.FirstOrDefault(t => t.POINT == 0xA2);
+
+ if(newLeadOutTrack.POINT != 0xA2)
+ {
+ AaruConsole.WriteLine("Cannot find lead-out...");
+ AaruConsole.WriteLine("Press any key to continue...");
+ System.Console.ReadLine();
+
+ goto parameters;
+ }
+
+ if(newLeadOutTrack.PMIN >= 0xA0 &&
+ !tocIsNotBcd)
+ newLeadOutTrack.PMIN -= 0x90;
+
+ if(newLeadOutTrack.PMIN != leadOutTrack.PMIN ||
+ newLeadOutTrack.PSEC != leadOutTrack.PSEC ||
+ newLeadOutTrack.PFRAME != leadOutTrack.PFRAME)
+ {
+ AaruConsole.WriteLine("Lead-out has changed, this drive does not support hot swapping discs...");
+ AaruConsole.WriteLine("Press any key to continue...");
+ System.Console.ReadLine();
+
+ goto parameters;
+ }
+
+ AaruConsole.Write("Reading LBA {0}... ", sectors + 5);
+
+ bool dataResult = dev.ReadCd(out byte[] dataBuffer, out byte[] dataSense, (uint)(sectors + 5), 2352, 1,
+ MmcSectorTypes.AllTypes, false, false, true, MmcHeaderCodes.AllHeaders, true,
+ true, MmcErrorField.None, MmcSubchannel.None, dev.Timeout, out _);
+
+ AaruConsole.WriteLine(dataResult ? "FAIL!" : "Success!");
+
+ AaruConsole.Write("Reading LBA {0} as audio (scrambled)... ", sectors + 5);
+
+ bool scrambledResult = dev.ReadCd(out byte[] scrambledBuffer, out byte[] scrambledSense,
+ (uint)(sectors + 5), 2352, 1, MmcSectorTypes.Cdda, false, false, false,
+ MmcHeaderCodes.None, true, false, MmcErrorField.None, MmcSubchannel.None,
+ dev.Timeout, out _);
+
+ AaruConsole.WriteLine(scrambledResult ? "FAIL!" : "Success!");
+
+ AaruConsole.Write("Reading LBA {0}'s PQ subchannel... ", sectors + 5);
+
+ bool pqResult = dev.ReadCd(out byte[] pqBuffer, out byte[] pqSense, (uint)(sectors + 5), 16, 1,
+ MmcSectorTypes.AllTypes, false, false, false, MmcHeaderCodes.None, false, false,
+ MmcErrorField.None, MmcSubchannel.Q16, dev.Timeout, out _);
+
+ if(pqResult)
+ pqResult = dev.ReadCd(out pqBuffer, out pqSense, (uint)(sectors + 5), 16, 1, MmcSectorTypes.AllTypes,
+ false, false, false, MmcHeaderCodes.None, false, false, MmcErrorField.None,
+ MmcSubchannel.Q16, dev.Timeout, out _);
+
+ AaruConsole.WriteLine(pqResult ? "FAIL!" : "Success!");
+
+ AaruConsole.Write("Reading LBA {0}'s PQ subchannel... ", sectors + 5);
+
+ bool rwResult = dev.ReadCd(out byte[] rwBuffer, out byte[] rwSense, (uint)(sectors + 5), 16, 1,
+ MmcSectorTypes.AllTypes, false, false, false, MmcHeaderCodes.None, false, false,
+ MmcErrorField.None, MmcSubchannel.Rw, dev.Timeout, out _);
+
+ if(rwResult)
+ rwResult = dev.ReadCd(out rwBuffer, out rwSense, (uint)(sectors + 5), 16, 1, MmcSectorTypes.Cdda, false,
+ false, false, MmcHeaderCodes.None, false, false, MmcErrorField.None,
+ MmcSubchannel.Rw, dev.Timeout, out _);
+
+ AaruConsole.WriteLine(pqResult ? "FAIL!" : "Success!");
+
+ menu:
+ System.Console.Clear();
+ AaruConsole.WriteLine("Device: {0}", devPath);
+ AaruConsole.WriteLine("Device {0} read Lead-Out.", dataResult && scrambledResult ? "cannot" : "can");
+
+ AaruConsole.WriteLine("LBA {0} sense is {1}, buffer is {2}, sense buffer is {3}.", sectors + 5, dataResult,
+ dataBuffer is null
+ ? "null"
+ : ArrayHelpers.ArrayIsNullOrEmpty(dataBuffer)
+ ? "empty"
+ : $"{dataBuffer.Length} bytes", dataSense is null
+ ? "null"
+ : ArrayHelpers.
+ ArrayIsNullOrEmpty(dataSense)
+ ? "empty"
+ : $"{dataSense.Length}");
+
+ AaruConsole.WriteLine("LBA {0} (scrambled) sense is {1}, buffer is {2}, sense buffer is {3}.", sectors + 5,
+ scrambledResult, scrambledBuffer is null
+ ? "null"
+ : ArrayHelpers.ArrayIsNullOrEmpty(scrambledBuffer)
+ ? "empty"
+ : $"{scrambledBuffer.Length} bytes", scrambledSense is null
+ ? "null"
+ : ArrayHelpers.
+ ArrayIsNullOrEmpty(scrambledSense)
+ ? "empty"
+ : $"{scrambledSense.Length}");
+
+ AaruConsole.WriteLine("LBA {0}'s PQ sense is {1}, buffer is {2}, sense buffer is {3}.", sectors + 5,
+ pqResult, pqBuffer is null
+ ? "null"
+ : ArrayHelpers.ArrayIsNullOrEmpty(pqBuffer)
+ ? "empty"
+ : $"{pqBuffer.Length} bytes", pqSense is null
+ ? "null"
+ : ArrayHelpers.
+ ArrayIsNullOrEmpty(pqSense)
+ ? "empty"
+ : $"{pqSense.Length}");
+
+ AaruConsole.WriteLine("LBA {0}'s RW sense is {1}, buffer is {2}, sense buffer is {3}.", sectors + 5,
+ dataResult, rwBuffer is null
+ ? "null"
+ : ArrayHelpers.ArrayIsNullOrEmpty(rwBuffer)
+ ? "empty"
+ : $"{rwBuffer.Length} bytes", rwSense is null
+ ? "null"
+ : ArrayHelpers.
+ ArrayIsNullOrEmpty(rwSense)
+ ? "empty"
+ : $"{rwSense.Length}");
+
+ AaruConsole.WriteLine();
+ AaruConsole.WriteLine("Choose what to do:");
+ AaruConsole.WriteLine("1.- Print LBA {0} buffer.", sectors + 5);
+ AaruConsole.WriteLine("2.- Print LBA {0} sense buffer.", sectors + 5);
+ AaruConsole.WriteLine("3.- Decode LBA {0} sense buffer.", sectors + 5);
+ AaruConsole.WriteLine("4.- Print LBA {0} (scrambled) buffer.", sectors + 5);
+ AaruConsole.WriteLine("5.- Print LBA {0} (scrambled) sense buffer.", sectors + 5);
+ AaruConsole.WriteLine("6.- Decode LBA {0} (scrambled) sense buffer.", sectors + 5);
+ AaruConsole.WriteLine("7.- Print LBA {0}'s PQ buffer.", sectors + 5);
+ AaruConsole.WriteLine("8.- Print LBA {0}'s PQ sense buffer.", sectors + 5);
+ AaruConsole.WriteLine("9.- Decode LBA {0}'s PQ sense buffer.", sectors + 5);
+ AaruConsole.WriteLine("10.- Print LBA {0}'s RW buffer.", sectors + 5);
+ AaruConsole.WriteLine("11.- Print LBA {0}'s RW sense buffer.", sectors + 5);
+ AaruConsole.WriteLine("12.- Decode LBA {0}'s RW sense buffer.", sectors + 5);
+ AaruConsole.WriteLine("13.- Send command again.");
+ AaruConsole.WriteLine("0.- Return to special SCSI MultiMedia Commands menu.");
+ AaruConsole.Write("Choose: ");
+
+ strDev = System.Console.ReadLine();
+
+ if(!int.TryParse(strDev, out item))
+ {
+ AaruConsole.WriteLine("Not a number. Press any key to continue...");
+ System.Console.ReadKey();
+ System.Console.Clear();
+
+ goto menu;
+ }
+
+ switch(item)
+ {
+ case 0:
+ AaruConsole.WriteLine("Returning to special SCSI MultiMedia Commands menu...");
+
+ return;
+ case 1:
+ System.Console.Clear();
+ AaruConsole.WriteLine("Device: {0}", devPath);
+ AaruConsole.WriteLine("LBA {0} response:", sectors + 5);
+
+ if(buffer != null)
+ PrintHex.PrintHexArray(dataBuffer, 64);
+
+ AaruConsole.WriteLine("Press any key to continue...");
+ System.Console.ReadKey();
+
+ goto menu;
+ case 2:
+ System.Console.Clear();
+ AaruConsole.WriteLine("Device: {0}", devPath);
+ AaruConsole.WriteLine("LBA {0} sense:", sectors + 5);
+
+ if(senseBuffer != null)
+ PrintHex.PrintHexArray(dataSense, 64);
+
+ AaruConsole.WriteLine("Press any key to continue...");
+ System.Console.ReadKey();
+
+ goto menu;
+ case 3:
+ System.Console.Clear();
+ AaruConsole.WriteLine("Device: {0}", devPath);
+ AaruConsole.WriteLine("LBA {0} decoded sense:", sectors + 5);
+ AaruConsole.Write("{0}", Sense.PrettifySense(dataSense));
+ AaruConsole.WriteLine("Press any key to continue...");
+ System.Console.ReadKey();
+
+ goto menu;
+ case 4:
+ System.Console.Clear();
+ AaruConsole.WriteLine("Device: {0}", devPath);
+ AaruConsole.WriteLine("LBA {0} (scrambled) response:", sectors + 5);
+
+ if(buffer != null)
+ PrintHex.PrintHexArray(scrambledBuffer, 64);
+
+ AaruConsole.WriteLine("Press any key to continue...");
+ System.Console.ReadKey();
+
+ goto menu;
+ case 5:
+ System.Console.Clear();
+ AaruConsole.WriteLine("Device: {0}", devPath);
+ AaruConsole.WriteLine("LBA {0} (scrambled) sense:", sectors + 5);
+
+ if(senseBuffer != null)
+ PrintHex.PrintHexArray(scrambledSense, 64);
+
+ AaruConsole.WriteLine("Press any key to continue...");
+ System.Console.ReadKey();
+
+ goto menu;
+ case 6:
+ System.Console.Clear();
+ AaruConsole.WriteLine("Device: {0}", devPath);
+ AaruConsole.WriteLine("LBA {0} (scrambled) decoded sense:", sectors + 5);
+ AaruConsole.Write("{0}", Sense.PrettifySense(scrambledSense));
+ AaruConsole.WriteLine("Press any key to continue...");
+ System.Console.ReadKey();
+
+ goto menu;
+ case 7:
+ System.Console.Clear();
+ AaruConsole.WriteLine("Device: {0}", devPath);
+ AaruConsole.WriteLine("LBA's PQ {0} response:", sectors + 5);
+
+ if(buffer != null)
+ PrintHex.PrintHexArray(pqBuffer, 64);
+
+ AaruConsole.WriteLine("Press any key to continue...");
+ System.Console.ReadKey();
+
+ goto menu;
+ case 8:
+ System.Console.Clear();
+ AaruConsole.WriteLine("Device: {0}", devPath);
+ AaruConsole.WriteLine("LBA's PQ {0} sense:", sectors + 5);
+
+ if(senseBuffer != null)
+ PrintHex.PrintHexArray(pqSense, 64);
+
+ AaruConsole.WriteLine("Press any key to continue...");
+ System.Console.ReadKey();
+
+ goto menu;
+ case 9:
+ System.Console.Clear();
+ AaruConsole.WriteLine("Device: {0}", devPath);
+ AaruConsole.WriteLine("LBA's PQ {0} decoded sense:", sectors + 5);
+ AaruConsole.Write("{0}", Sense.PrettifySense(pqSense));
+ AaruConsole.WriteLine("Press any key to continue...");
+ System.Console.ReadKey();
+
+ goto menu;
+ case 10:
+ System.Console.Clear();
+ AaruConsole.WriteLine("Device: {0}", devPath);
+ AaruConsole.WriteLine("LBA's RW {0} response:", sectors + 5);
+
+ if(buffer != null)
+ PrintHex.PrintHexArray(rwBuffer, 64);
+
+ AaruConsole.WriteLine("Press any key to continue...");
+ System.Console.ReadKey();
+
+ goto menu;
+ case 11:
+ System.Console.Clear();
+ AaruConsole.WriteLine("Device: {0}", devPath);
+ AaruConsole.WriteLine("LBA's RW {0} sense:", sectors + 5);
+
+ if(senseBuffer != null)
+ PrintHex.PrintHexArray(rwSense, 64);
+
+ AaruConsole.WriteLine("Press any key to continue...");
+ System.Console.ReadKey();
+
+ goto menu;
+ case 12:
+ System.Console.Clear();
+ AaruConsole.WriteLine("Device: {0}", devPath);
+ AaruConsole.WriteLine("LBA's RW {0} decoded sense:", sectors + 5);
+ AaruConsole.Write("{0}", Sense.PrettifySense(rwSense));
+ AaruConsole.WriteLine("Press any key to continue...");
+ System.Console.ReadKey();
+
+ goto menu;
+ case 13: goto start;
+ default:
+ AaruConsole.WriteLine("Incorrect option. Press any key to continue...");
+ System.Console.ReadKey();
+ System.Console.Clear();
+
+ goto menu;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Aaru.Tests.Devices/SCSI_MMC/SCSI_MMC.cs b/Aaru.Tests.Devices/SCSI_MMC/SCSI_MMC.cs
index e57f493d3..2d3e52d26 100644
--- a/Aaru.Tests.Devices/SCSI_MMC/SCSI_MMC.cs
+++ b/Aaru.Tests.Devices/SCSI_MMC/SCSI_MMC.cs
@@ -45,6 +45,7 @@ namespace Aaru.Tests.Devices
WriteLine("1.- Try to read the cache data from a device with a MediaTek chipset (F1h command 06h subcommand).");
AaruConsole.WriteLine("2.- Try to read a GD-ROM using a trap disc.");
+ AaruConsole.WriteLine("3.- Try to read Lead-Out using a trap disc.");
AaruConsole.WriteLine("0.- Return to command class menu.");
AaruConsole.Write("Choose: ");
@@ -72,6 +73,10 @@ namespace Aaru.Tests.Devices
case 2:
CheckGdromReadability(devPath, dev);
+ continue;
+ case 3:
+ ReadLeadOutUsingTrapDisc(devPath, dev);
+
continue;
default:
AaruConsole.WriteLine("Incorrect option. Press any key to continue...");