diff --git a/Aaru.Core/Aaru.Core.csproj.DotSettings b/Aaru.Core/Aaru.Core.csproj.DotSettings
index 16bfff059..ce5fbb3bc 100644
--- a/Aaru.Core/Aaru.Core.csproj.DotSettings
+++ b/Aaru.Core/Aaru.Core.csproj.DotSettings
@@ -1,5 +1,6 @@
True
+ True
True
True
True
\ No newline at end of file
diff --git a/Aaru.Core/Devices/Dumping/Dump.cs b/Aaru.Core/Devices/Dumping/Dump.cs
index b2ff07ec4..906e7a37e 100644
--- a/Aaru.Core/Devices/Dumping/Dump.cs
+++ b/Aaru.Core/Devices/Dumping/Dump.cs
@@ -223,38 +223,45 @@ namespace Aaru.Core.Devices.Dumping
_maximumReadable = (uint)_dbDev.OptimalMultipleSectorsRead;
}
- if(_dev.IsUsb &&
- _dev.UsbVendorId == 0x054C &&
- (_dev.UsbProductId == 0x01C8 || _dev.UsbProductId == 0x01C9 || _dev.UsbProductId == 0x02D2))
- PlayStationPortable();
- else
- switch(_dev.Type)
- {
- case DeviceType.ATA:
- Ata();
+ switch(_dev.IsUsb)
+ {
+ case true when _dev.UsbVendorId == 0x054C && _dev.UsbProductId is 0x01C8 or 0x01C9 or 0x02D2: PlayStationPortable();
- break;
- case DeviceType.MMC:
- case DeviceType.SecureDigital:
- SecureDigital();
+ break;
+ case true when _dev.UsbVendorId ==0x0403 && _dev.UsbProductId ==0x97C1: Retrode();
- break;
- case DeviceType.NVMe:
- NVMe();
+ break;
+ default:
+ switch(_dev.Type)
+ {
+ case DeviceType.ATA:
+ Ata();
- break;
- case DeviceType.ATAPI:
- case DeviceType.SCSI:
- Scsi();
+ break;
+ case DeviceType.MMC:
+ case DeviceType.SecureDigital:
+ SecureDigital();
- break;
- default:
- _dumpLog.WriteLine("Unknown device type.");
- _dumpLog.Close();
- StoppingErrorMessage?.Invoke("Unknown device type.");
+ break;
+ case DeviceType.NVMe:
+ NVMe();
- return;
- }
+ break;
+ case DeviceType.ATAPI:
+ case DeviceType.SCSI:
+ Scsi();
+
+ break;
+ default:
+ _dumpLog.WriteLine("Unknown device type.");
+ _dumpLog.Close();
+ StoppingErrorMessage?.Invoke("Unknown device type.");
+
+ return;
+ }
+
+ break;
+ }
_errorLog.Close();
_dumpLog.Close();
diff --git a/Aaru.Core/Devices/Dumping/LinearMemory/Retrode.cs b/Aaru.Core/Devices/Dumping/LinearMemory/Retrode.cs
new file mode 100644
index 000000000..7cf65d032
--- /dev/null
+++ b/Aaru.Core/Devices/Dumping/LinearMemory/Retrode.cs
@@ -0,0 +1,359 @@
+// /***************************************************************************
+// Aaru Data Preservation Suite
+// ----------------------------------------------------------------------------
+//
+// Filename : Retrode.cs
+// Author(s) : Natalia Portillo
+//
+// Component : Dumping SNES/MD/GEN/MS/N64/GB/GB/GBA carts with a Retrode.
+//
+// --[ Description ] ----------------------------------------------------------
+//
+// Handles dumping using a Retrode.
+//
+// --[ License ] --------------------------------------------------------------
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+//
+// ----------------------------------------------------------------------------
+// Copyright © 2011-2021 Natalia Portillo
+// ****************************************************************************/
+
+using System;
+using System.Linq;
+using Aaru.CommonTypes;
+using Aaru.CommonTypes.Enums;
+using Aaru.CommonTypes.Interfaces;
+using Version = Aaru.CommonTypes.Interop.Version;
+
+namespace Aaru.Core.Devices.Dumping;
+
+public partial class Dump
+{
+ static readonly byte[] _sfcExtension =
+ {
+ 0x53, 0x46, 0x43
+ };
+
+ /// Dumps a CFW PlayStation Portable UMD
+ void Retrode()
+ {
+ bool sense = _dev.Read10(out byte[] buffer, out _, 0, false, true, false, false, 0, 512, 0, 1, _dev.Timeout,
+ out _);
+
+ if(sense)
+ {
+ _dumpLog.WriteLine("Could not read...");
+ StoppingErrorMessage?.Invoke("Could not read...");
+
+ return;
+ }
+
+ byte[] tmp = new byte[8];
+
+ Array.Copy(buffer, 0x36, tmp, 0, 8);
+
+ // UMDs are stored inside a FAT16 volume
+ if(!tmp.SequenceEqual(_fatSignature))
+ {
+ _dumpLog.WriteLine("Retrode partition not recognized, not dumping...");
+ StoppingErrorMessage?.Invoke("Retrode partition not recognized, not dumping...");
+
+ return;
+ }
+
+ ushort fatStart = (ushort)((buffer[0x0F] << 8) + buffer[0x0E]);
+ ushort sectorsPerFat = (ushort)((buffer[0x17] << 8) + buffer[0x16]);
+ ushort rootStart = (ushort)((sectorsPerFat * 2) + fatStart);
+ ushort rootSize = (ushort)(((buffer[0x12] << 8) + buffer[0x11]) * 32 / 512);
+ byte sectorsPerCluster = buffer[0x0D];
+
+ UpdateStatus?.Invoke($"Reading root directory in sector {rootStart}...");
+ _dumpLog.WriteLine("Reading root directory in sector {0}...", rootStart);
+
+ sense = _dev.Read10(out buffer, out _, 0, false, true, false, false, rootStart, 512, 0, 1, _dev.Timeout, out _);
+
+ if(sense)
+ {
+ StoppingErrorMessage?.Invoke("Could not read...");
+ _dumpLog.WriteLine("Could not read...");
+
+ return;
+ }
+
+ int romPos;
+ bool sfcFound = false;
+ tmp = new byte[3];
+
+ for(romPos = 0; romPos < buffer.Length; romPos += 0x20)
+ {
+ Array.Copy(buffer, romPos + 8, tmp, 0, 3);
+
+ if(!tmp.SequenceEqual(_sfcExtension))
+ continue;
+
+ sfcFound = true;
+
+ break;
+ }
+
+ if(!sfcFound)
+ {
+ StoppingErrorMessage?.Invoke("No cartridge found, not dumping...");
+ _dumpLog.WriteLine("No cartridge found, not dumping...");
+
+ return;
+ }
+
+ ushort cluster = BitConverter.ToUInt16(buffer, romPos + 0x1A);
+ uint romSize = BitConverter.ToUInt32(buffer, romPos + 0x1C);
+
+ const MediaType mediaType = MediaType.SNESGamePak;
+ uint blocksToRead = 64;
+ double totalDuration = 0;
+ double currentSpeed = 0;
+ double maxSpeed = double.MinValue;
+ double minSpeed = double.MaxValue;
+ byte[] senseBuf;
+
+ if(_outputPlugin is not IByteAddressableImage outputBai ||
+ !outputBai.SupportedMediaTypes.Contains(mediaType))
+ {
+ _dumpLog.WriteLine("The specified format does not support the inserted cartridge...");
+ StoppingErrorMessage?.Invoke("The specified format does not support the inserted cartridge...");
+
+ return;
+ }
+
+ sense = _dev.Read10(out byte[] readBuffer, out _, 0, false, true, false, false, 0, 512, 0, 1, _dev.Timeout,
+ out _);
+
+ if(sense)
+ {
+ _dumpLog.WriteLine("Could not read...");
+ StoppingErrorMessage?.Invoke("Could not read...");
+
+ return;
+ }
+
+ uint startSector = (uint)(rootStart + rootSize + ((cluster - 2) * sectorsPerCluster));
+ uint romSectors = romSize / 512;
+ uint romRemaining = romSize % 512;
+
+ if(romSize > 1073741824)
+ UpdateStatus?.Invoke($"Cartridge has {romSize} bytes ({romSize / 1073741824d:F3} GiB)");
+ else if(romSize > 1048576)
+ UpdateStatus?.Invoke($"Cartridge has {romSize} bytes ({romSize / 1048576d:F3} MiB)");
+ else if(romSize > 1024)
+ UpdateStatus?.Invoke($"Cartridge has {romSize} bytes ({romSize / 1024d:F3} KiB)");
+ else
+ UpdateStatus?.Invoke($"Cartridge has {romSize} bytes");
+
+ UpdateStatus?.Invoke($"Media identified as {mediaType}.");
+ _dumpLog.WriteLine("Media identified as {0}.", mediaType);
+
+ ErrorNumber ret = outputBai.Create(_outputPath, mediaType, _formatOptions, romSize);
+
+ // Cannot create image
+ if(ret != ErrorNumber.NoError)
+ {
+ _dumpLog.WriteLine("Error {0} creating output image, not continuing.", ret);
+ _dumpLog.WriteLine(outputBai.ErrorMessage);
+
+ StoppingErrorMessage?.Invoke("Error creating output image, not continuing." + Environment.NewLine +
+ outputBai.ErrorMessage);
+
+ return;
+ }
+
+ DateTime start = DateTime.UtcNow;
+ double imageWriteDuration = 0;
+
+ DateTime timeSpeedStart = DateTime.UtcNow;
+ ulong sectorSpeedStart = 0;
+ InitProgress?.Invoke();
+
+ for(ulong i = 0; i < romSectors; i += blocksToRead)
+ {
+ if(_aborted)
+ {
+ UpdateStatus?.Invoke("Aborted!");
+ _dumpLog.WriteLine("Aborted!");
+
+ break;
+ }
+
+ if(romSectors - i < blocksToRead)
+ blocksToRead = (uint)(romSectors - i);
+
+ if(currentSpeed > maxSpeed &&
+ currentSpeed > 0)
+ maxSpeed = currentSpeed;
+
+ if(currentSpeed < minSpeed &&
+ currentSpeed > 0)
+ minSpeed = currentSpeed;
+
+ UpdateProgress?.Invoke($"Reading byte {i * 512} of {romSize} ({currentSpeed:F3} MiB/sec.)", (long)i * 512,
+ romSize);
+
+ sense = _dev.Read10(out readBuffer, out senseBuf, 0, false, true, false, false, (uint)(startSector + i),
+ 512, 0, (ushort)blocksToRead, _dev.Timeout, out double cmdDuration);
+
+ totalDuration += cmdDuration;
+
+ if(!sense &&
+ !_dev.Error)
+ {
+ DateTime writeStart = DateTime.Now;
+ outputBai.WriteBytes(readBuffer, 0, readBuffer.Length, out _);
+ imageWriteDuration += (DateTime.Now - writeStart).TotalSeconds;
+ }
+ else
+ {
+ _errorLog?.WriteLine(i, _dev.Error, _dev.LastError, senseBuf);
+
+ // TODO: Reset device after X errors
+ if(_stopOnError)
+ return; // TODO: Return more cleanly
+
+ _dumpLog.WriteLine("Skipping {0} bytes from errored byte {1}.", _skip * 512, i * 512);
+ i += _skip - blocksToRead;
+ }
+
+ sectorSpeedStart += blocksToRead;
+
+ double elapsed = (DateTime.UtcNow - timeSpeedStart).TotalSeconds;
+
+ if(elapsed <= 0)
+ continue;
+
+ currentSpeed = sectorSpeedStart * 512 / (1048576 * elapsed);
+ sectorSpeedStart = 0;
+ timeSpeedStart = DateTime.UtcNow;
+ }
+
+ if(romRemaining > 0 &&
+ !_aborted)
+ {
+ if(currentSpeed > maxSpeed &&
+ currentSpeed > 0)
+ maxSpeed = currentSpeed;
+
+ if(currentSpeed < minSpeed &&
+ currentSpeed > 0)
+ minSpeed = currentSpeed;
+
+ UpdateProgress?.Invoke($"Reading byte {romSectors * 512} of {romSize} ({currentSpeed:F3} MiB/sec.)",
+ (long)romSectors * 512, romSize);
+
+ sense = _dev.Read10(out readBuffer, out senseBuf, 0, false, true, false, false, romSectors, 512, 0, 1,
+ _dev.Timeout, out double cmdDuration);
+
+ totalDuration += cmdDuration;
+
+ if(!sense &&
+ !_dev.Error)
+ {
+ DateTime writeStart = DateTime.Now;
+ outputBai.WriteBytes(readBuffer, 0, (int)romRemaining, out _);
+ imageWriteDuration += (DateTime.Now - writeStart).TotalSeconds;
+ }
+ else
+ {
+ _errorLog?.WriteLine(romSectors, _dev.Error, _dev.LastError, senseBuf);
+
+ // TODO: Reset device after X errors
+ if(_stopOnError)
+ return; // TODO: Return more cleanly
+
+ _dumpLog.WriteLine("Skipping {0} bytes from errored byte {1}.", _skip * 512, romSectors * 512);
+ }
+ }
+
+ DateTime end = DateTime.UtcNow;
+ EndProgress?.Invoke();
+
+ UpdateStatus?.Invoke($"Dump finished in {(end - start).TotalSeconds} seconds.");
+
+ UpdateStatus?.
+ Invoke($"Average dump speed {512 * (double)(romSectors + 1) / 1024 / (totalDuration / 1000):F3} KiB/sec.");
+
+ UpdateStatus?.
+ Invoke($"Average write speed {512 * (double)(romSectors + 1) / 1024 / imageWriteDuration:F3} KiB/sec.");
+
+ _dumpLog.WriteLine("Dump finished in {0} seconds.", (end - start).TotalSeconds);
+
+ _dumpLog.WriteLine("Average dump speed {0:F3} KiB/sec.",
+ 512 * (double)(romSectors + 1) / 1024 / (totalDuration / 1000));
+
+ _dumpLog.WriteLine("Average write speed {0:F3} KiB/sec.",
+ 512 * (double)(romSectors + 1) / 1024 / imageWriteDuration);
+
+ var metadata = new CommonTypes.Structs.ImageInfo
+ {
+ Application = "Aaru",
+ ApplicationVersion = Version.GetVersion()
+ };
+
+ if(!outputBai.SetMetadata(metadata))
+ ErrorMessage?.Invoke("Error {0} setting metadata, continuing..." + Environment.NewLine +
+ outputBai.ErrorMessage);
+
+ // TODO: Set dump hardware
+ //outputBAI.SetDumpHardware();
+
+ if(_preSidecar != null)
+ outputBai.SetCicmMetadata(_preSidecar);
+
+ _dumpLog.WriteLine("Closing output file.");
+ UpdateStatus?.Invoke("Closing output file.");
+ DateTime closeStart = DateTime.Now;
+ outputBai.Close();
+ DateTime closeEnd = DateTime.Now;
+ _dumpLog.WriteLine("Closed in {0} seconds.", (closeEnd - closeStart).TotalSeconds);
+
+ if(_aborted)
+ {
+ UpdateStatus?.Invoke("Aborted!");
+ _dumpLog.WriteLine("Aborted!");
+
+ return;
+ }
+
+ double totalChkDuration = 0;
+
+ /* TODO: Create sidecar
+ if(_metadata)
+ WriteOpticalSidecar(blockSize, blocks, mediaType, null, null, 1, out totalChkDuration, null);
+ */
+ UpdateStatus?.Invoke("");
+
+ UpdateStatus?.
+ Invoke($"Took a total of {(end - start).TotalSeconds:F3} seconds ({totalDuration / 1000:F3} processing commands, {totalChkDuration / 1000:F3} checksumming, {imageWriteDuration:F3} writing, {(closeEnd - closeStart).TotalSeconds:F3} closing).");
+
+ UpdateStatus?.
+ Invoke($"Average speed: {512 * (double)(romSectors + 1) / 1048576 / (totalDuration / 1000):F3} MiB/sec.");
+
+ if(maxSpeed > 0)
+ UpdateStatus?.Invoke($"Fastest speed burst: {maxSpeed:F3} MiB/sec.");
+
+ if(minSpeed is > 0 and < double.MaxValue)
+ UpdateStatus?.Invoke($"Slowest speed burst: {minSpeed:F3} MiB/sec.");
+
+ UpdateStatus?.Invoke("");
+
+ Statistics.AddMedia(mediaType, true);
+ }
+}
\ No newline at end of file
diff --git a/Aaru.sln.DotSettings b/Aaru.sln.DotSettings
index 5ed079eab..c16f8d749 100644
--- a/Aaru.sln.DotSettings
+++ b/Aaru.sln.DotSettings
@@ -502,6 +502,7 @@
True
True
True
+ True
True
True
True