diff --git a/DiscImageChef.Core/Devices/Dumping/PlayStationPortable.cs b/DiscImageChef.Core/Devices/Dumping/PlayStationPortable.cs
new file mode 100644
index 000000000..a57b30306
--- /dev/null
+++ b/DiscImageChef.Core/Devices/Dumping/PlayStationPortable.cs
@@ -0,0 +1,707 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Xml.Serialization;
+using DiscImageChef.CommonTypes;
+using DiscImageChef.CommonTypes.Enums;
+using DiscImageChef.CommonTypes.Extents;
+using DiscImageChef.CommonTypes.Interfaces;
+using DiscImageChef.CommonTypes.Metadata;
+using DiscImageChef.CommonTypes.Structs;
+using DiscImageChef.Console;
+using DiscImageChef.Core.Logging;
+using DiscImageChef.Decoders.SCSI;
+using DiscImageChef.Devices;
+using Schemas;
+using MediaType = DiscImageChef.CommonTypes.MediaType;
+using TrackType = DiscImageChef.CommonTypes.Enums.TrackType;
+using Version = DiscImageChef.CommonTypes.Interop.Version;
+
+namespace DiscImageChef.Core.Devices.Dumping
+{
+ public static class PlayStationPortable
+ {
+ static readonly byte[] FatSignature = {0x46, 0x41, 0x54, 0x31, 0x36, 0x20, 0x20, 0x20};
+ static readonly byte[] IsoExtension = {0x49, 0x53, 0x4F};
+
+ ///
+ /// Dumps a CFW PlayStation Portable UMD
+ ///
+ /// Device
+ /// Path to the device
+ /// Prefix for output data files
+ /// Plugin for output file
+ /// How many times to retry
+ /// Force to continue dump whenever possible
+ /// Store whatever data the drive returned on error
+ /// Stop dump on first error
+ /// Information for dump resuming
+ /// Dump logger
+ /// Encoding to use when analyzing dump
+ /// Path to output file
+ /// Formats to pass to output file plugin
+ /// Existing sidecar
+ /// How many sectors to skip on errors
+ /// Don't create metadata sidecar
+ /// Don't trim errors
+ /// If you asked to dump long sectors from a SCSI Streaming device
+ public static void Dump(Device dev, string devicePath,
+ IWritableImage outputPlugin, ushort retryPasses,
+ bool force, bool persistent,
+ bool stopOnError, ref Resume resume, ref DumpLog dumpLog,
+ Encoding encoding, string outputPrefix,
+ string outputPath,
+ Dictionary formatOptions, CICMMetadataType preSidecar,
+ uint skip,
+ bool nometadata, bool notrim)
+ {
+ if(!outputPlugin.SupportedMediaTypes.Contains(MediaType.MemoryStickDuo) &&
+ !outputPlugin.SupportedMediaTypes.Contains(MediaType.MemoryStickProDuo) &&
+ !outputPlugin.SupportedMediaTypes.Contains(MediaType.UMD))
+ {
+ DicConsole.WriteLine("Selected output plugin does not support MemoryStick Duo or UMD, cannot dump...");
+ dumpLog.WriteLine("Selected output plugin does not support MemoryStick Duo or UMD, cannot dump...");
+ return;
+ }
+
+ DicConsole.WriteLine("Checking if media is UMD or MemoryStick...");
+ dumpLog.WriteLine("Checking if media is UMD or MemoryStick...");
+
+ bool sense = dev.ModeSense6(out byte[] buffer, out _, false, ScsiModeSensePageControl.Current, 0,
+ dev.Timeout, out _);
+
+ if(sense)
+ {
+ DicConsole.WriteLine("Could not get MODE SENSE...");
+ dumpLog.WriteLine("Could not get MODE SENSE...");
+ return;
+ }
+
+ Modes.DecodedMode? decoded = Modes.DecodeMode6(buffer, PeripheralDeviceTypes.DirectAccess);
+
+ if(!decoded.HasValue)
+ {
+ DicConsole.WriteLine("Could not decode MODE SENSE...");
+ dumpLog.WriteLine("Could not decode MODE SENSE...");
+ return;
+ }
+
+ // UMDs are always write protected
+ if(!decoded.Value.Header.WriteProtected)
+ {
+ DumpMs(dev, devicePath, outputPlugin, retryPasses, force, persistent, stopOnError,
+ ref resume,
+ ref dumpLog, encoding, outputPrefix, outputPath, formatOptions, preSidecar, skip,
+ nometadata,
+ notrim);
+ return;
+ }
+
+ sense = dev.Read12(out buffer, out _, 0, false, true, false, false, 0, 512, 0, 1, false, dev.Timeout,
+ out _);
+
+ if(sense)
+ {
+ DicConsole.WriteLine("Could not read...");
+ dumpLog.WriteLine("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))
+ {
+ DumpMs(dev, devicePath, outputPlugin, retryPasses, force, persistent, stopOnError,
+ ref resume,
+ ref dumpLog, encoding, outputPrefix, outputPath, formatOptions, preSidecar, skip,
+ nometadata,
+ notrim);
+ return;
+ }
+
+ ushort fatStart = (ushort)((buffer[0x0F] << 8) + buffer[0x0E]);
+ ushort sectorsPerFat = (ushort)((buffer[0x17] << 8) + buffer[0x16]);
+ ushort rootStart = (ushort)(sectorsPerFat * 2 + fatStart);
+
+ DicConsole.WriteLine("Reading root directory in sector {0}...", rootStart);
+ dumpLog.WriteLine("Reading root directory in sector {0}...", rootStart);
+
+ sense = dev.Read12(out buffer, out _, 0, false, true, false, false, rootStart, 512, 0, 1, false,
+ dev.Timeout, out _);
+
+ if(sense)
+ {
+ DicConsole.WriteLine("Could not read...");
+ dumpLog.WriteLine("Could not read...");
+ return;
+ }
+
+ tmp = new byte[3];
+ Array.Copy(buffer, 0x28, tmp, 0, 3);
+
+ if(!tmp.SequenceEqual(IsoExtension))
+ {
+ DumpMs(dev, devicePath, outputPlugin, retryPasses, force, persistent, stopOnError,
+ ref resume,
+ ref dumpLog, encoding, outputPrefix, outputPath, formatOptions, preSidecar, skip,
+ nometadata,
+ notrim);
+ return;
+ }
+
+ DicConsole.WriteLine("FAT starts at sector {0} and runs for {1} sectors...", fatStart, sectorsPerFat);
+ dumpLog.WriteLine("FAT starts at sector {0} and runs for {1} sectors...", fatStart, sectorsPerFat);
+
+ DicConsole.WriteLine("Reading FAT...");
+ dumpLog.WriteLine("Reading FAT...");
+
+ byte[] fat = new byte[sectorsPerFat * 512];
+
+ uint position = 0;
+
+ while(position < sectorsPerFat)
+ {
+ uint transfer = 64;
+ if(transfer + position > sectorsPerFat) transfer = sectorsPerFat - position;
+ sense = dev.Read12(out buffer, out _, 0, false, true, false, false, position + fatStart, 512, 0,
+ transfer, false, dev.Timeout, out _);
+
+ if(sense)
+ {
+ DicConsole.WriteLine("Could not read...");
+ dumpLog.WriteLine("Could not read...");
+ return;
+ }
+
+ Array.Copy(buffer, 0, fat, position * 512, transfer * 512);
+
+ position += transfer;
+ }
+
+ DicConsole.WriteLine("Traversing FAT...");
+ dumpLog.WriteLine("Traversing FAT...");
+
+ ushort previousCluster = BitConverter.ToUInt16(fat, 4);
+
+ for(int i = 3; i < fat.Length / 2; i++)
+ {
+ ushort nextCluster = BitConverter.ToUInt16(fat, i * 2);
+
+ if(nextCluster == previousCluster + 1)
+ {
+ previousCluster = nextCluster;
+ continue;
+ }
+
+ if(nextCluster == 0xFFFF) break;
+
+ DumpMs(dev, devicePath, outputPlugin, retryPasses, force, persistent, stopOnError,
+ ref resume,
+ ref dumpLog, encoding, outputPrefix, outputPath, formatOptions, preSidecar, skip,
+ nometadata,
+ notrim);
+ return;
+ }
+
+ DumpUmd(dev, devicePath, outputPlugin, retryPasses, force, persistent, stopOnError,
+ ref resume, ref dumpLog,
+ encoding, outputPrefix, outputPath, formatOptions, preSidecar, skip, nometadata, notrim);
+ }
+
+ static void DumpUmd(Device dev, string devicePath,
+ IWritableImage outputPlugin, ushort retryPasses, bool force,
+ bool persistent, bool stopOnError,
+ ref Resume resume, ref DumpLog dumpLog,
+ Encoding encoding, string outputPrefix,
+ string outputPath,
+ Dictionary formatOptions, CICMMetadataType preSidecar,
+ uint skip,
+ bool nometadata, bool notrim)
+ {
+ const uint BLOCK_SIZE = 2048;
+ const MediaType DSK_TYPE = MediaType.UMD;
+ uint blocksToRead = 16;
+ double totalDuration = 0;
+ double currentSpeed = 0;
+ double maxSpeed = double.MinValue;
+ double minSpeed = double.MaxValue;
+ bool aborted = false;
+ DateTime start;
+ DateTime end;
+ System.Console.CancelKeyPress += (sender, e) => e.Cancel = aborted = true;
+
+ bool sense = dev.Read12(out byte[] readBuffer, out _, 0, false, true, false, false, 0, 512, 0, 1, false,
+ dev.Timeout, out _);
+
+ if(sense)
+ {
+ DicConsole.WriteLine("Could not read...");
+ dumpLog.WriteLine("Could not read...");
+ return;
+ }
+
+ ushort fatStart = (ushort)((readBuffer[0x0F] << 8) + readBuffer[0x0E]);
+ ushort sectorsPerFat = (ushort)((readBuffer[0x17] << 8) + readBuffer[0x16]);
+ ushort rootStart = (ushort)(sectorsPerFat * 2 + fatStart);
+ ushort rootSize = (ushort)(((readBuffer[0x12] << 8) + readBuffer[0x11]) * 32 / 512);
+ ushort umdStart = (ushort)(rootStart + rootSize);
+
+ DicConsole.WriteLine("Reading root directory in sector {0}...", rootStart);
+ dumpLog.WriteLine("Reading root directory in sector {0}...", rootStart);
+
+ sense = dev.Read12(out readBuffer, out _, 0, false, true, false, false, rootStart, 512, 0, 1, false,
+ dev.Timeout, out _);
+
+ if(sense)
+ {
+ DicConsole.WriteLine("Could not read...");
+ dumpLog.WriteLine("Could not read...");
+ return;
+ }
+
+ uint umdSizeInBytes = BitConverter.ToUInt32(readBuffer, 0x3C);
+ ulong blocks = umdSizeInBytes / BLOCK_SIZE;
+ string mediaPartNumber = Encoding.ASCII.GetString(readBuffer, 0, 11).Trim();
+
+ DicConsole.WriteLine("Media has {0} blocks of {1} bytes/each. (for a total of {2} bytes)", blocks,
+ BLOCK_SIZE, blocks * (ulong)BLOCK_SIZE);
+
+ dumpLog.WriteLine("Device reports {0} blocks ({1} bytes).", blocks, blocks * BLOCK_SIZE);
+ dumpLog.WriteLine("Device can read {0} blocks at a time.", blocksToRead);
+ dumpLog.WriteLine("Device reports {0} bytes per logical block.", BLOCK_SIZE);
+ dumpLog.WriteLine("Device reports {0} bytes per physical block.", 2048);
+ dumpLog.WriteLine("SCSI device type: {0}.", dev.ScsiType);
+ dumpLog.WriteLine("Media identified as {0}.", DSK_TYPE);
+ dumpLog.WriteLine("Media part number is {0}.", mediaPartNumber);
+ DicConsole.WriteLine("Media identified as {0}", DSK_TYPE);
+ DicConsole.WriteLine("Media part number is {0}", mediaPartNumber);
+
+ bool ret;
+
+ DicConsole.WriteLine("Reading {0} sectors at a time.", blocksToRead);
+
+ MhddLog mhddLog = new MhddLog(outputPrefix + ".mhddlog.bin", dev, blocks, BLOCK_SIZE, blocksToRead);
+ IbgLog ibgLog = new IbgLog(outputPrefix + ".ibg", 0x0010);
+ ret = outputPlugin.Create(outputPath, DSK_TYPE, formatOptions, blocks, BLOCK_SIZE);
+
+ // Cannot create image
+ if(!ret)
+ {
+ dumpLog.WriteLine("Error creating output image, not continuing.");
+ dumpLog.WriteLine(outputPlugin.ErrorMessage);
+ DicConsole.ErrorWriteLine("Error creating output image, not continuing.");
+ DicConsole.ErrorWriteLine(outputPlugin.ErrorMessage);
+ return;
+ }
+
+ start = DateTime.UtcNow;
+ double imageWriteDuration = 0;
+
+ outputPlugin.SetTracks(new List