Exemple #1
0
        /// <summary>Creates optical metadata sidecar</summary>
        /// <param name="blockSize">Size of the read sector in bytes</param>
        /// <param name="blocks">Total number of positive sectors</param>
        /// <param name="mediaType">Disc type</param>
        /// <param name="layers">Disc layers</param>
        /// <param name="mediaTags">Media tags</param>
        /// <param name="sessions">Disc sessions</param>
        /// <param name="totalChkDuration">Total time spent doing checksums</param>
        /// <param name="discOffset">Disc write offset</param>
        void WriteOpticalSidecar(uint blockSize, ulong blocks, MediaType mediaType, LayersType layers,
                                 Dictionary <MediaTagType, byte[]> mediaTags, int sessions, out double totalChkDuration,
                                 int?discOffset)
        {
            _dumpLog.WriteLine("Creating sidecar.");
            var         filters     = new FiltersList();
            IFilter     filter      = filters.GetFilter(_outputPath);
            IMediaImage inputPlugin = ImageFormat.Detect(filter);

            totalChkDuration = 0;

            if (!inputPlugin.Open(filter))
            {
                StoppingErrorMessage?.Invoke("Could not open created image.");

                return;
            }

            DateTime chkStart = DateTime.UtcNow;

            // ReSharper disable once UseObjectOrCollectionInitializer
            _sidecarClass = new Sidecar(inputPlugin, _outputPath, filter.Id, _encoding);
            _sidecarClass.InitProgressEvent    += InitProgress;
            _sidecarClass.UpdateProgressEvent  += UpdateProgress;
            _sidecarClass.EndProgressEvent     += EndProgress;
            _sidecarClass.InitProgressEvent2   += InitProgress2;
            _sidecarClass.UpdateProgressEvent2 += UpdateProgress2;
            _sidecarClass.EndProgressEvent2    += EndProgress2;
            _sidecarClass.UpdateStatusEvent    += UpdateStatus;
            CICMMetadataType sidecar = _sidecarClass.Create();
            DateTime         end     = DateTime.UtcNow;

            totalChkDuration = (end - chkStart).TotalMilliseconds;
            _dumpLog.WriteLine("Sidecar created in {0} seconds.", (end - chkStart).TotalSeconds);

            _dumpLog.WriteLine("Average checksum speed {0:F3} KiB/sec.",
                               ((double)blockSize * (double)(blocks + 1)) / 1024 / (totalChkDuration / 1000));

            if (_preSidecar != null)
            {
                _preSidecar.OpticalDisc = sidecar.OpticalDisc;
                sidecar = _preSidecar;
            }

            List <(ulong start, string type)> filesystems = new List <(ulong start, string type)>();

            if (sidecar.OpticalDisc[0].Track != null)
            {
                filesystems.AddRange(from xmlTrack in sidecar.OpticalDisc[0].Track
                                     where xmlTrack.FileSystemInformation != null
                                     from partition in xmlTrack.FileSystemInformation
                                     where partition.FileSystems != null from fileSystem in partition.FileSystems
                                     select(partition.StartSector, fileSystem.Type));
            }

            if (filesystems.Count > 0)
            {
                foreach (var filesystem in filesystems.Select(o => new
                {
                    o.start, o.type
                }).Distinct())
                {
                    _dumpLog.WriteLine("Found filesystem {0} at sector {1}", filesystem.type, filesystem.start);
                }
            }

            sidecar.OpticalDisc[0].Dimensions        = Dimensions.DimensionsFromMediaType(mediaType);
            (string type, string subType)discType    = CommonTypes.Metadata.MediaType.MediaTypeToString(mediaType);
            sidecar.OpticalDisc[0].DiscType          = discType.type;
            sidecar.OpticalDisc[0].DiscSubType       = discType.subType;
            sidecar.OpticalDisc[0].DumpHardwareArray = _resume.Tries.ToArray();
            sidecar.OpticalDisc[0].Sessions          = (uint)sessions;
            sidecar.OpticalDisc[0].Layers            = layers;

            if (discOffset.HasValue)
            {
                sidecar.OpticalDisc[0].Offset          = (int)(discOffset / 4);
                sidecar.OpticalDisc[0].OffsetSpecified = true;
            }

            if (mediaTags != null)
            {
                foreach (KeyValuePair <MediaTagType, byte[]> tag in mediaTags.Where(tag => _outputPlugin.
                                                                                    SupportedMediaTags.
                                                                                    Contains(tag.Key)))
                {
                    AddMediaTagToSidecar(_outputPath, tag, ref sidecar);
                }
            }

            UpdateStatus?.Invoke("Writing metadata sidecar");

            var xmlFs = new FileStream(_outputPrefix + ".cicm.xml", FileMode.Create);

            var xmlSer = new XmlSerializer(typeof(CICMMetadataType));

            xmlSer.Serialize(xmlFs, sidecar);
            xmlFs.Close();
        }
        /// <summary>
        ///     Creates a metadata sidecar for an optical disc (e.g. CD, DVD, GD, BD, XGD, GOD)
        /// </summary>
        /// <param name="image">Image</param>
        /// <param name="filterId">Filter uuid</param>
        /// <param name="imagePath">Image path</param>
        /// <param name="fi">Image file information</param>
        /// <param name="plugins">Image plugins</param>
        /// <param name="imgChecksums">List of image checksums</param>
        /// <param name="sidecar">Metadata sidecar</param>
        static void OpticalDisc(IMediaImage image, Guid filterId, string imagePath,
                                FileInfo fi, PluginBase plugins,
                                List <ChecksumType> imgChecksums, ref CICMMetadataType sidecar, Encoding encoding)
        {
            sidecar.OpticalDisc = new[]
            {
                new OpticalDiscType
                {
                    Checksums = imgChecksums.ToArray(),
                    Image     = new ImageType
                    {
                        format          = image.Format,
                        offset          = 0,
                        offsetSpecified = true,
                        Value           = Path.GetFileName(imagePath)
                    },
                    Size     = fi.Length,
                    Sequence = new SequenceType {
                        MediaTitle = image.Info.MediaTitle
                    }
                }
            };

            if (image.Info.MediaSequence != 0 && image.Info.LastMediaSequence != 0)
            {
                sidecar.OpticalDisc[0].Sequence.MediaSequence = image.Info.MediaSequence;
                sidecar.OpticalDisc[0].Sequence.TotalMedia    = image.Info.LastMediaSequence;
            }
            else
            {
                sidecar.OpticalDisc[0].Sequence.MediaSequence = 1;
                sidecar.OpticalDisc[0].Sequence.TotalMedia    = 1;
            }

            MediaType dskType = image.Info.MediaType;

            foreach (MediaTagType tagType in image.Info.ReadableMediaTags)
            {
                switch (tagType)
                {
                case MediaTagType.CD_ATIP:
                    sidecar.OpticalDisc[0].ATIP = new DumpType
                    {
                        Image     = Path.GetFileName(imagePath),
                        Checksums = Checksum.GetChecksums(image.ReadDiskTag(MediaTagType.CD_ATIP)).ToArray(),
                        Size      = image.ReadDiskTag(MediaTagType.CD_ATIP).Length
                    };
                    ATIP.CDATIP?atip = ATIP.Decode(image.ReadDiskTag(MediaTagType.CD_ATIP));
                    if (atip.HasValue)
                    {
                        if (atip.Value.DDCD)
                        {
                            dskType = atip.Value.DiscType ? MediaType.DDCDRW : MediaType.DDCDR;
                        }
                        else
                        {
                            dskType = atip.Value.DiscType ? MediaType.CDRW : MediaType.CDR;
                        }
                    }
                    break;

                case MediaTagType.DVD_BCA:
                    sidecar.OpticalDisc[0].BCA = new DumpType
                    {
                        Image     = Path.GetFileName(imagePath),
                        Checksums = Checksum.GetChecksums(image.ReadDiskTag(MediaTagType.DVD_BCA)).ToArray(),
                        Size      = image.ReadDiskTag(MediaTagType.DVD_BCA).Length
                    };
                    break;

                case MediaTagType.BD_BCA:
                    sidecar.OpticalDisc[0].BCA = new DumpType
                    {
                        Image     = Path.GetFileName(imagePath),
                        Checksums = Checksum.GetChecksums(image.ReadDiskTag(MediaTagType.BD_BCA)).ToArray(),
                        Size      = image.ReadDiskTag(MediaTagType.BD_BCA).Length
                    };
                    break;

                case MediaTagType.DVD_CMI:
                    sidecar.OpticalDisc[0].CMI = new DumpType
                    {
                        Image     = Path.GetFileName(imagePath),
                        Checksums = Checksum.GetChecksums(image.ReadDiskTag(MediaTagType.DVD_CMI)).ToArray(),
                        Size      = image.ReadDiskTag(MediaTagType.DVD_CMI).Length
                    };
                    CSS_CPRM.LeadInCopyright?cmi =
                        CSS_CPRM.DecodeLeadInCopyright(image.ReadDiskTag(MediaTagType.DVD_CMI));
                    if (cmi.HasValue)
                    {
                        switch (cmi.Value.CopyrightType)
                        {
                        case CopyrightType.AACS:
                            sidecar.OpticalDisc[0].CopyProtection = "AACS";
                            break;

                        case CopyrightType.CSS:
                            sidecar.OpticalDisc[0].CopyProtection = "CSS";
                            break;

                        case CopyrightType.CPRM:
                            sidecar.OpticalDisc[0].CopyProtection = "CPRM";
                            break;
                        }
                    }

                    break;

                case MediaTagType.DVD_DMI:
                    sidecar.OpticalDisc[0].DMI = new DumpType
                    {
                        Image     = Path.GetFileName(imagePath),
                        Checksums = Checksum.GetChecksums(image.ReadDiskTag(MediaTagType.DVD_DMI)).ToArray(),
                        Size      = image.ReadDiskTag(MediaTagType.DVD_DMI).Length
                    };
                    if (DMI.IsXbox(image.ReadDiskTag(MediaTagType.DVD_DMI)))
                    {
                        dskType = MediaType.XGD;
                        sidecar.OpticalDisc[0].Dimensions = new DimensionsType {
                            Diameter = 120, Thickness = 1.2
                        };
                    }
                    else if (DMI.IsXbox360(image.ReadDiskTag(MediaTagType.DVD_DMI)))
                    {
                        dskType = MediaType.XGD2;
                        sidecar.OpticalDisc[0].Dimensions = new DimensionsType {
                            Diameter = 120, Thickness = 1.2
                        };
                    }

                    break;

                case MediaTagType.DVD_PFI:
                    sidecar.OpticalDisc[0].PFI = new DumpType
                    {
                        Image     = Path.GetFileName(imagePath),
                        Checksums = Checksum.GetChecksums(image.ReadDiskTag(MediaTagType.DVD_PFI)).ToArray(),
                        Size      = image.ReadDiskTag(MediaTagType.DVD_PFI).Length
                    };
                    PFI.PhysicalFormatInformation?pfi = PFI.Decode(image.ReadDiskTag(MediaTagType.DVD_PFI));
                    if (pfi.HasValue)
                    {
                        if (dskType != MediaType.XGD && dskType != MediaType.XGD2 && dskType != MediaType.XGD3)
                        {
                            switch (pfi.Value.DiskCategory)
                            {
                            case DiskCategory.DVDPR:
                                dskType = MediaType.DVDPR;
                                break;

                            case DiskCategory.DVDPRDL:
                                dskType = MediaType.DVDPRDL;
                                break;

                            case DiskCategory.DVDPRW:
                                dskType = MediaType.DVDPRW;
                                break;

                            case DiskCategory.DVDPRWDL:
                                dskType = MediaType.DVDPRWDL;
                                break;

                            case DiskCategory.DVDR:
                                dskType = MediaType.DVDR;
                                break;

                            case DiskCategory.DVDRAM:
                                dskType = MediaType.DVDRAM;
                                break;

                            case DiskCategory.DVDROM:
                                dskType = MediaType.DVDROM;
                                break;

                            case DiskCategory.DVDRW:
                                dskType = MediaType.DVDRW;
                                break;

                            case DiskCategory.HDDVDR:
                                dskType = MediaType.HDDVDR;
                                break;

                            case DiskCategory.HDDVDRAM:
                                dskType = MediaType.HDDVDRAM;
                                break;

                            case DiskCategory.HDDVDROM:
                                dskType = MediaType.HDDVDROM;
                                break;

                            case DiskCategory.HDDVDRW:
                                dskType = MediaType.HDDVDRW;
                                break;

                            case DiskCategory.Nintendo:
                                dskType = MediaType.GOD;
                                break;

                            case DiskCategory.UMD:
                                dskType = MediaType.UMD;
                                break;
                            }

                            if (dskType == MediaType.DVDR && pfi.Value.PartVersion == 6)
                            {
                                dskType = MediaType.DVDRDL;
                            }
                            if (dskType == MediaType.DVDRW && pfi.Value.PartVersion == 3)
                            {
                                dskType = MediaType.DVDRWDL;
                            }
                            if (dskType == MediaType.GOD && pfi.Value.DiscSize == DVDSize.OneTwenty)
                            {
                                dskType = MediaType.WOD;
                            }

                            sidecar.OpticalDisc[0].Dimensions = new DimensionsType();
                            if (dskType == MediaType.UMD)
                            {
                                sidecar.OpticalDisc[0].Dimensions.Height          = 64;
                                sidecar.OpticalDisc[0].Dimensions.HeightSpecified = true;
                                sidecar.OpticalDisc[0].Dimensions.Width           = 63;
                                sidecar.OpticalDisc[0].Dimensions.WidthSpecified  = true;
                                sidecar.OpticalDisc[0].Dimensions.Thickness       = 4;
                            }
                            else
                            {
                                switch (pfi.Value.DiscSize)
                                {
                                case DVDSize.Eighty:
                                    sidecar.OpticalDisc[0].Dimensions.Diameter          = 80;
                                    sidecar.OpticalDisc[0].Dimensions.DiameterSpecified = true;
                                    sidecar.OpticalDisc[0].Dimensions.Thickness         = 1.2;
                                    break;

                                case DVDSize.OneTwenty:
                                    sidecar.OpticalDisc[0].Dimensions.Diameter          = 120;
                                    sidecar.OpticalDisc[0].Dimensions.DiameterSpecified = true;
                                    sidecar.OpticalDisc[0].Dimensions.Thickness         = 1.2;
                                    break;
                                }
                            }
                        }
                    }

                    break;

                case MediaTagType.CD_PMA:
                    sidecar.OpticalDisc[0].PMA = new DumpType
                    {
                        Image     = Path.GetFileName(imagePath),
                        Checksums = Checksum.GetChecksums(image.ReadDiskTag(MediaTagType.CD_PMA)).ToArray(),
                        Size      = image.ReadDiskTag(MediaTagType.CD_PMA).Length
                    };
                    break;

                case MediaTagType.CD_FullTOC:
                    sidecar.OpticalDisc[0].TOC = new DumpType
                    {
                        Image     = Path.GetFileName(imagePath),
                        Checksums = Checksum.GetChecksums(image.ReadDiskTag(MediaTagType.CD_FullTOC)).ToArray(),
                        Size      = image.ReadDiskTag(MediaTagType.CD_FullTOC).Length
                    };
                    break;

                case MediaTagType.CD_LeadIn:
                    sidecar.OpticalDisc[0].LeadIn = new[]
                    {
                        new BorderType
                        {
                            Image     = Path.GetFileName(imagePath),
                            Checksums = Checksum.GetChecksums(image.ReadDiskTag(MediaTagType.CD_LeadIn)).ToArray(),
                            Size      = image.ReadDiskTag(MediaTagType.CD_LeadIn).Length
                        }
                    };
                    break;

                case MediaTagType.Xbox_SecuritySector:
                    if (sidecar.OpticalDisc[0].Xbox == null)
                    {
                        sidecar.OpticalDisc[0].Xbox = new XboxType();
                    }

                    sidecar.OpticalDisc[0].Xbox.SecuritySectors = new[]
                    {
                        new XboxSecuritySectorsType
                        {
                            RequestNumber   = 0,
                            RequestVersion  = 1,
                            SecuritySectors = new DumpType
                            {
                                Image     = Path.GetFileName(imagePath),
                                Checksums =
                                    Checksum.GetChecksums(image.ReadDiskTag(MediaTagType.Xbox_SecuritySector))
                                    .ToArray(),
                                Size = image.ReadDiskTag(MediaTagType.Xbox_SecuritySector).Length
                            }
                        }
                    };

                    break;

                case MediaTagType.Xbox_PFI:
                    if (sidecar.OpticalDisc[0].Xbox == null)
                    {
                        sidecar.OpticalDisc[0].Xbox = new XboxType();
                    }

                    sidecar.OpticalDisc[0].Xbox.PFI = new DumpType
                    {
                        Image     = Path.GetFileName(imagePath),
                        Checksums = Checksum.GetChecksums(image.ReadDiskTag(MediaTagType.Xbox_PFI)).ToArray(),
                        Size      = image.ReadDiskTag(MediaTagType.Xbox_PFI).Length
                    };
                    break;

                case MediaTagType.Xbox_DMI:
                    if (sidecar.OpticalDisc[0].Xbox == null)
                    {
                        sidecar.OpticalDisc[0].Xbox = new XboxType();
                    }

                    sidecar.OpticalDisc[0].Xbox.DMI = new DumpType
                    {
                        Image     = Path.GetFileName(imagePath),
                        Checksums = Checksum.GetChecksums(image.ReadDiskTag(MediaTagType.Xbox_DMI)).ToArray(),
                        Size      = image.ReadDiskTag(MediaTagType.Xbox_DMI).Length
                    };
                    break;
                }
            }

            try
            {
                List <Session> sessions = image.Sessions;
                sidecar.OpticalDisc[0].Sessions = sessions?.Count ?? 1;
            }
            catch { sidecar.OpticalDisc[0].Sessions = 1; }

            List <Track>     tracks  = image.Tracks;
            List <TrackType> trksLst = null;

            if (tracks != null)
            {
                sidecar.OpticalDisc[0].Tracks    = new int[1];
                sidecar.OpticalDisc[0].Tracks[0] = tracks.Count;
                trksLst = new List <TrackType>();
            }

            if (sidecar.OpticalDisc[0].Dimensions == null && image.Info.MediaType != MediaType.Unknown)
            {
                sidecar.OpticalDisc[0].Dimensions = Dimensions.DimensionsFromMediaType(image.Info.MediaType);
            }

            InitProgress();

            UpdateStatus("Checking filesystems");
            List <Partition> partitions = Partitions.GetAll(image);

            Partitions.AddSchemesToStats(partitions);

            foreach (Track trk in tracks)
            {
                TrackType xmlTrk = new TrackType();
                switch (trk.TrackType)
                {
                case CommonTypes.Enums.TrackType.Audio:
                    xmlTrk.TrackType1 = TrackTypeTrackType.audio;
                    break;

                case CommonTypes.Enums.TrackType.CdMode2Form2:
                    xmlTrk.TrackType1 = TrackTypeTrackType.m2f2;
                    break;

                case CommonTypes.Enums.TrackType.CdMode2Formless:
                    xmlTrk.TrackType1 = TrackTypeTrackType.mode2;
                    break;

                case CommonTypes.Enums.TrackType.CdMode2Form1:
                    xmlTrk.TrackType1 = TrackTypeTrackType.m2f1;
                    break;

                case CommonTypes.Enums.TrackType.CdMode1:
                    xmlTrk.TrackType1 = TrackTypeTrackType.mode1;
                    break;

                case CommonTypes.Enums.TrackType.Data:
                    switch (sidecar.OpticalDisc[0].DiscType)
                    {
                    case "BD":
                        xmlTrk.TrackType1 = TrackTypeTrackType.bluray;
                        break;

                    case "DDCD":
                        xmlTrk.TrackType1 = TrackTypeTrackType.ddcd;
                        break;

                    case "DVD":
                        xmlTrk.TrackType1 = TrackTypeTrackType.dvd;
                        break;

                    case "HD DVD":
                        xmlTrk.TrackType1 = TrackTypeTrackType.hddvd;
                        break;

                    default:
                        xmlTrk.TrackType1 = TrackTypeTrackType.mode1;
                        break;
                    }

                    break;
                }

                xmlTrk.Sequence =
                    new TrackSequenceType {
                    Session = trk.TrackSession, TrackNumber = (int)trk.TrackSequence
                };
                xmlTrk.StartSector = (long)trk.TrackStartSector;
                xmlTrk.EndSector   = (long)trk.TrackEndSector;

                if (trk.Indexes != null && trk.Indexes.ContainsKey(0))
                {
                    if (trk.Indexes.TryGetValue(0, out ulong idx0))
                    {
                        xmlTrk.StartSector = (long)idx0;
                    }
                }

                switch (sidecar.OpticalDisc[0].DiscType)
                {
                case "CD":
                case "GD":
                    xmlTrk.StartMSF = LbaToMsf(xmlTrk.StartSector);
                    xmlTrk.EndMSF   = LbaToMsf(xmlTrk.EndSector);
                    break;

                case "DDCD":
                    xmlTrk.StartMSF = DdcdLbaToMsf(xmlTrk.StartSector);
                    xmlTrk.EndMSF   = DdcdLbaToMsf(xmlTrk.EndSector);
                    break;
                }

                xmlTrk.Image = new ImageType {
                    Value = Path.GetFileName(trk.TrackFile), format = trk.TrackFileType
                };

                if (trk.TrackFileOffset > 0)
                {
                    xmlTrk.Image.offset          = (long)trk.TrackFileOffset;
                    xmlTrk.Image.offsetSpecified = true;
                }

                xmlTrk.Size           = (xmlTrk.EndSector - xmlTrk.StartSector + 1) * trk.TrackRawBytesPerSector;
                xmlTrk.BytesPerSector = trk.TrackBytesPerSector;

                uint  sectorsToRead = 512;
                ulong sectors       = (ulong)(xmlTrk.EndSector - xmlTrk.StartSector + 1);
                ulong doneSectors   = 0;

                // If there is only one track, and it's the same as the image file (e.g. ".iso" files), don't re-checksum.
                if (image.Id == new Guid("12345678-AAAA-BBBB-CCCC-123456789000") &&
                    // Only if filter is none...
                    (filterId == new Guid("12345678-AAAA-BBBB-CCCC-123456789000") ||
                     // ...or AppleDouble
                     filterId == new Guid("1b2165ee-c9df-4b21-bbbb-9e5892b2df4d")))
                {
                    xmlTrk.Checksums = sidecar.OpticalDisc[0].Checksums;
                }
                else
                {
                    UpdateProgress("Track {0} of {1}", trk.TrackSequence, tracks.Count);

                    // For fast debugging, skip checksum
                    //goto skipChecksum;

                    Checksum trkChkWorker = new Checksum();

                    InitProgress2();
                    while (doneSectors < sectors)
                    {
                        byte[] sector;

                        if (sectors - doneSectors >= sectorsToRead)
                        {
                            sector = image.ReadSectorsLong(doneSectors, sectorsToRead,
                                                           (uint)xmlTrk.Sequence.TrackNumber);
                            UpdateProgress2("Hashings sector {0} of {1}", (long)doneSectors,
                                            (long)(trk.TrackEndSector - trk.TrackStartSector + 1));
                            doneSectors += sectorsToRead;
                        }
                        else
                        {
                            sector = image.ReadSectorsLong(doneSectors, (uint)(sectors - doneSectors),
                                                           (uint)xmlTrk.Sequence.TrackNumber);
                            UpdateProgress2("Hashings sector {0} of {1}", (long)doneSectors,
                                            (long)(trk.TrackEndSector - trk.TrackStartSector + 1));
                            doneSectors += sectors - doneSectors;
                        }

                        trkChkWorker.Update(sector);
                    }

                    List <ChecksumType> trkChecksums = trkChkWorker.End();

                    xmlTrk.Checksums = trkChecksums.ToArray();

                    EndProgress2();
                }

                if (trk.TrackSubchannelType != TrackSubchannelType.None)
                {
                    xmlTrk.SubChannel = new SubChannelType
                    {
                        Image = new ImageType {
                            Value = trk.TrackSubchannelFile
                        },
                        // TODO: Packed subchannel has different size?
                        Size = (xmlTrk.EndSector - xmlTrk.StartSector + 1) * 96
                    };

                    switch (trk.TrackSubchannelType)
                    {
                    case TrackSubchannelType.Packed:
                    case TrackSubchannelType.PackedInterleaved:
                        xmlTrk.SubChannel.Image.format = "rw";
                        break;

                    case TrackSubchannelType.Raw:
                    case TrackSubchannelType.RawInterleaved:
                        xmlTrk.SubChannel.Image.format = "rw_raw";
                        break;

                    case TrackSubchannelType.Q16:
                    case TrackSubchannelType.Q16Interleaved:
                        xmlTrk.SubChannel.Image.format = "q16";
                        break;
                    }

                    if (trk.TrackFileOffset > 0)
                    {
                        xmlTrk.SubChannel.Image.offset          = (long)trk.TrackSubchannelOffset;
                        xmlTrk.SubChannel.Image.offsetSpecified = true;
                    }

                    Checksum subChkWorker = new Checksum();

                    sectors     = (ulong)(xmlTrk.EndSector - xmlTrk.StartSector + 1);
                    doneSectors = 0;

                    InitProgress2();
                    while (doneSectors < sectors)
                    {
                        byte[] sector;

                        if (sectors - doneSectors >= sectorsToRead)
                        {
                            sector = image.ReadSectorsTag(doneSectors, sectorsToRead, (uint)xmlTrk.Sequence.TrackNumber,
                                                          SectorTagType.CdSectorSubchannel);
                            UpdateProgress2("Hashings subchannel sector {0} of {1}", (long)doneSectors,
                                            (long)(trk.TrackEndSector - trk.TrackStartSector + 1));
                            doneSectors += sectorsToRead;
                        }
                        else
                        {
                            sector = image.ReadSectorsTag(doneSectors, (uint)(sectors - doneSectors),
                                                          (uint)xmlTrk.Sequence.TrackNumber,
                                                          SectorTagType.CdSectorSubchannel);
                            UpdateProgress2("Hashings subchannel sector {0} of {1}", (long)doneSectors,
                                            (long)(trk.TrackEndSector - trk.TrackStartSector + 1));
                            doneSectors += sectors - doneSectors;
                        }

                        subChkWorker.Update(sector);
                    }

                    List <ChecksumType> subChecksums = subChkWorker.End();

                    xmlTrk.SubChannel.Checksums = subChecksums.ToArray();

                    EndProgress2();
                }

                // For fast debugging, skip checksum
                //skipChecksum:

                List <Partition> trkPartitions = partitions
                                                 .Where(p => p.Start >= trk.TrackStartSector &&
                                                        p.End <= trk.TrackEndSector).ToList();

                xmlTrk.FileSystemInformation = new PartitionType[1];
                if (trkPartitions.Count > 0)
                {
                    xmlTrk.FileSystemInformation = new PartitionType[trkPartitions.Count];
                    for (int i = 0; i < trkPartitions.Count; i++)
                    {
                        xmlTrk.FileSystemInformation[i] = new PartitionType
                        {
                            Description = trkPartitions[i].Description,
                            EndSector   = (int)trkPartitions[i].End,
                            Name        = trkPartitions[i].Name,
                            Sequence    = (int)trkPartitions[i].Sequence,
                            StartSector = (int)trkPartitions[i].Start,
                            Type        = trkPartitions[i].Type
                        };
                        List <FileSystemType> lstFs = new List <FileSystemType>();

                        foreach (IFilesystem plugin in plugins.PluginsList.Values)
                        {
                            try
                            {
                                if (!plugin.Identify(image, trkPartitions[i]))
                                {
                                    continue;
                                }

                                plugin.GetInformation(image, trkPartitions[i], out _, encoding);
                                lstFs.Add(plugin.XmlFsType);
                                Statistics.AddFilesystem(plugin.XmlFsType.Type);

                                switch (plugin.XmlFsType.Type)
                                {
                                case "Opera":
                                    dskType = MediaType.ThreeDO;
                                    break;

                                case "PC Engine filesystem":
                                    dskType = MediaType.SuperCDROM2;
                                    break;

                                case "Nintendo Wii filesystem":
                                    dskType = MediaType.WOD;
                                    break;

                                case "Nintendo Gamecube filesystem":
                                    dskType = MediaType.GOD;
                                    break;
                                }
                            }
                        }
Exemple #3
0
        /// <summary>
        ///     Dumps an Xbox Game Disc using a Kreon drive
        /// </summary>
        /// <param name="dev">Device</param>
        /// <param name="devicePath">Path to the device</param>
        /// <param name="outputPrefix">Prefix for output data files</param>
        /// <param name="outputPlugin">Plugin for output file</param>
        /// <param name="retryPasses">How many times to retry</param>
        /// <param name="force">Force to continue dump whenever possible</param>
        /// <param name="dumpRaw">Dump raw/long sectors</param>
        /// <param name="persistent">Store whatever data the drive returned on error</param>
        /// <param name="stopOnError">Stop dump on first error</param>
        /// <param name="resume">Information for dump resuming</param>
        /// <param name="dumpLog">Dump logger</param>
        /// <param name="encoding">Encoding to use when analyzing dump</param>
        /// <param name="mediaTags">Media tags as retrieved in MMC layer</param>
        /// <param name="dskType">Disc type as detected in MMC layer</param>
        /// <param name="outputPath">Path to output file</param>
        /// <param name="formatOptions">Formats to pass to output file plugin</param>
        /// <exception cref="InvalidOperationException">
        ///     If the provided resume does not correspond with the current in progress
        ///     dump
        /// </exception>
        internal static void Dump(Device dev, string devicePath,
                                  IWritableImage outputPlugin, ushort retryPasses,
                                  bool force, bool dumpRaw,
                                  bool persistent, bool stopOnError,
                                  Dictionary <MediaTagType, byte[]> mediaTags, ref MediaType dskType,
                                  ref Resume resume,
                                  ref DumpLog dumpLog, Encoding encoding,
                                  string outputPrefix, string outputPath,
                                  Dictionary <string, string> formatOptions, CICMMetadataType preSidecar,
                                  uint skip,
                                  bool nometadata, bool notrim)
        {
            bool       sense;
            const uint BLOCK_SIZE   = 2048;
            uint       blocksToRead = 64;
            DateTime   start;
            DateTime   end;
            double     totalDuration = 0;
            double     currentSpeed  = 0;
            double     maxSpeed      = double.MinValue;
            double     minSpeed      = double.MaxValue;
            bool       aborted       = false;

            System.Console.CancelKeyPress += (sender, e) => e.Cancel = aborted = true;

            if (mediaTags.ContainsKey(MediaTagType.DVD_PFI))
            {
                mediaTags.Remove(MediaTagType.DVD_PFI);
            }
            if (mediaTags.ContainsKey(MediaTagType.DVD_DMI))
            {
                mediaTags.Remove(MediaTagType.DVD_DMI);
            }

            dumpLog.WriteLine("Reading Xbox Security Sector.");
            sense = dev.KreonExtractSs(out byte[] ssBuf, out byte[] senseBuf, dev.Timeout, out _);
            if (sense)
            {
                dumpLog.WriteLine("Cannot get Xbox Security Sector, not continuing.");
                DicConsole.ErrorWriteLine("Cannot get Xbox Security Sector, not continuing.");
                return;
            }

            dumpLog.WriteLine("Decoding Xbox Security Sector.");
            SS.SecuritySector?xboxSs = SS.Decode(ssBuf);
            if (!xboxSs.HasValue)
            {
                dumpLog.WriteLine("Cannot decode Xbox Security Sector, not continuing.");
                DicConsole.ErrorWriteLine("Cannot decode Xbox Security Sector, not continuing.");
                return;
            }

            byte[] tmpBuf = new byte[ssBuf.Length - 4];
            Array.Copy(ssBuf, 4, tmpBuf, 0, ssBuf.Length - 4);
            mediaTags.Add(MediaTagType.Xbox_SecuritySector, tmpBuf);

            // Get video partition size
            DicConsole.DebugWriteLine("Dump-media command", "Getting video partition size");
            dumpLog.WriteLine("Locking drive.");
            sense = dev.KreonLock(out senseBuf, dev.Timeout, out _);
            if (sense)
            {
                dumpLog.WriteLine("Cannot lock drive, not continuing.");
                DicConsole.ErrorWriteLine("Cannot lock drive, not continuing.");
                return;
            }

            dumpLog.WriteLine("Getting video partition size.");
            sense = dev.ReadCapacity(out byte[] readBuffer, out senseBuf, dev.Timeout, out _);
            if (sense)
            {
                dumpLog.WriteLine("Cannot get disc capacity.");
                DicConsole.ErrorWriteLine("Cannot get disc capacity.");
                return;
            }

            ulong totalSize =
                (ulong)((readBuffer[0] << 24) + (readBuffer[1] << 16) + (readBuffer[2] << 8) + readBuffer[3]);

            dumpLog.WriteLine("Reading Physical Format Information.");
            sense = dev.ReadDiscStructure(out readBuffer, out senseBuf, MmcDiscStructureMediaType.Dvd, 0, 0,
                                          MmcDiscStructureFormat.PhysicalInformation, 0, 0, out _);
            if (sense)
            {
                dumpLog.WriteLine("Cannot get PFI.");
                DicConsole.ErrorWriteLine("Cannot get PFI.");
                return;
            }

            tmpBuf = new byte[readBuffer.Length - 4];
            Array.Copy(readBuffer, 4, tmpBuf, 0, readBuffer.Length - 4);
            mediaTags.Add(MediaTagType.DVD_PFI, tmpBuf);
            DicConsole.DebugWriteLine("Dump-media command", "Video partition total size: {0} sectors", totalSize);
            ulong l0Video = PFI.Decode(readBuffer).Value.Layer0EndPSN - PFI.Decode(readBuffer).Value.DataAreaStartPSN +
                            1;
            ulong l1Video = totalSize - l0Video + 1;

            dumpLog.WriteLine("Reading Disc Manufacturing Information.");
            sense = dev.ReadDiscStructure(out readBuffer, out senseBuf, MmcDiscStructureMediaType.Dvd, 0, 0,
                                          MmcDiscStructureFormat.DiscManufacturingInformation, 0, 0, out _);
            if (sense)
            {
                dumpLog.WriteLine("Cannot get DMI.");
                DicConsole.ErrorWriteLine("Cannot get DMI.");
                return;
            }

            tmpBuf = new byte[readBuffer.Length - 4];
            Array.Copy(readBuffer, 4, tmpBuf, 0, readBuffer.Length - 4);
            mediaTags.Add(MediaTagType.DVD_DMI, tmpBuf);

            // Get game partition size
            DicConsole.DebugWriteLine("Dump-media command", "Getting game partition size");
            dumpLog.WriteLine("Unlocking drive (Xtreme).");
            sense = dev.KreonUnlockXtreme(out senseBuf, dev.Timeout, out _);
            if (sense)
            {
                dumpLog.WriteLine("Cannot unlock drive, not continuing.");
                DicConsole.ErrorWriteLine("Cannot unlock drive, not continuing.");
                return;
            }

            dumpLog.WriteLine("Getting game partition size.");
            sense = dev.ReadCapacity(out readBuffer, out senseBuf, dev.Timeout, out _);
            if (sense)
            {
                dumpLog.WriteLine("Cannot get disc capacity.");
                DicConsole.ErrorWriteLine("Cannot get disc capacity.");
                return;
            }

            ulong gameSize =
                (ulong)((readBuffer[0] << 24) + (readBuffer[1] << 16) + (readBuffer[2] << 8) + readBuffer[3]) + 1;

            DicConsole.DebugWriteLine("Dump-media command", "Game partition total size: {0} sectors", gameSize);

            // Get middle zone size
            DicConsole.DebugWriteLine("Dump-media command", "Getting middle zone size");
            dumpLog.WriteLine("Unlocking drive (Wxripper).");
            sense = dev.KreonUnlockWxripper(out senseBuf, dev.Timeout, out _);
            if (sense)
            {
                dumpLog.WriteLine("Cannot unlock drive, not continuing.");
                DicConsole.ErrorWriteLine("Cannot unlock drive, not continuing.");
                return;
            }

            dumpLog.WriteLine("Getting disc size.");
            sense = dev.ReadCapacity(out readBuffer, out senseBuf, dev.Timeout, out _);
            if (sense)
            {
                dumpLog.WriteLine("Cannot get disc capacity.");
                DicConsole.ErrorWriteLine("Cannot get disc capacity.");
                return;
            }

            totalSize = (ulong)((readBuffer[0] << 24) + (readBuffer[1] << 16) + (readBuffer[2] << 8) + readBuffer[3]);
            dumpLog.WriteLine("Reading Physical Format Information.");
            sense = dev.ReadDiscStructure(out readBuffer, out senseBuf, MmcDiscStructureMediaType.Dvd, 0, 0,
                                          MmcDiscStructureFormat.PhysicalInformation, 0, 0, out _);
            if (sense)
            {
                dumpLog.WriteLine("Cannot get PFI.");
                DicConsole.ErrorWriteLine("Cannot get PFI.");
                return;
            }

            DicConsole.DebugWriteLine("Dump-media command", "Unlocked total size: {0} sectors", totalSize);
            ulong blocks     = totalSize + 1;
            ulong middleZone =
                totalSize - (PFI.Decode(readBuffer).Value.Layer0EndPSN -
                             PFI.Decode(readBuffer).Value.DataAreaStartPSN +
                             1) - gameSize + 1;

            tmpBuf = new byte[readBuffer.Length - 4];
            Array.Copy(readBuffer, 4, tmpBuf, 0, readBuffer.Length - 4);
            mediaTags.Add(MediaTagType.Xbox_PFI, tmpBuf);

            dumpLog.WriteLine("Reading Disc Manufacturing Information.");
            sense = dev.ReadDiscStructure(out readBuffer, out senseBuf, MmcDiscStructureMediaType.Dvd, 0, 0,
                                          MmcDiscStructureFormat.DiscManufacturingInformation, 0, 0, out _);
            if (sense)
            {
                dumpLog.WriteLine("Cannot get DMI.");
                DicConsole.ErrorWriteLine("Cannot get DMI.");
                return;
            }

            tmpBuf = new byte[readBuffer.Length - 4];
            Array.Copy(readBuffer, 4, tmpBuf, 0, readBuffer.Length - 4);
            mediaTags.Add(MediaTagType.Xbox_DMI, tmpBuf);

            totalSize = l0Video + l1Video + middleZone * 2 + gameSize;
            ulong layerBreak = l0Video + middleZone + gameSize / 2;

            DicConsole.WriteLine("Video layer 0 size: {0} sectors", l0Video);
            DicConsole.WriteLine("Video layer 1 size: {0} sectors", l1Video);
            DicConsole.WriteLine("Middle zone size: {0} sectors", middleZone);
            DicConsole.WriteLine("Game data size: {0} sectors", gameSize);
            DicConsole.WriteLine("Total size: {0} sectors", totalSize);
            DicConsole.WriteLine("Real layer break: {0}", layerBreak);
            DicConsole.WriteLine();

            dumpLog.WriteLine("Video layer 0 size: {0} sectors", l0Video);
            dumpLog.WriteLine("Video layer 1 size: {0} sectors", l1Video);
            dumpLog.WriteLine("Middle zone 0 size: {0} sectors", middleZone);
            dumpLog.WriteLine("Game data 0 size: {0} sectors", gameSize);
            dumpLog.WriteLine("Total 0 size: {0} sectors", totalSize);
            dumpLog.WriteLine("Real layer break: {0}", layerBreak);

            bool read12 = !dev.Read12(out readBuffer, out senseBuf, 0, false, true, false, false, 0, BLOCK_SIZE, 0, 1,
                                      false, dev.Timeout, out _);

            if (!read12)
            {
                dumpLog.WriteLine("Cannot read medium, aborting scan...");
                DicConsole.ErrorWriteLine("Cannot read medium, aborting scan...");
                return;
            }

            dumpLog.WriteLine("Using SCSI READ (12) command.");
            DicConsole.WriteLine("Using SCSI READ (12) command.");

            while (true)
            {
                if (read12)
                {
                    sense = dev.Read12(out readBuffer, out senseBuf, 0, false, false, false, false, 0, BLOCK_SIZE, 0,
                                       blocksToRead, false, dev.Timeout, out _);
                    if (sense || dev.Error)
                    {
                        blocksToRead /= 2;
                    }
                }

                if (!dev.Error || blocksToRead == 1)
                {
                    break;
                }
            }

            if (dev.Error)
            {
                dumpLog.WriteLine("Device error {0} trying to guess ideal transfer length.", dev.LastError);
                DicConsole.ErrorWriteLine("Device error {0} trying to guess ideal transfer length.", dev.LastError);
                return;
            }

            if (skip < blocksToRead)
            {
                skip = blocksToRead;
            }

            bool ret = true;

            foreach (MediaTagType tag in mediaTags.Keys)
            {
                if (outputPlugin.SupportedMediaTags.Contains(tag))
                {
                    continue;
                }

                ret = false;
                dumpLog.WriteLine($"Output format does not support {tag}.");
                DicConsole.ErrorWriteLine($"Output format does not support {tag}.");
            }

            if (!ret)
            {
                dumpLog.WriteLine("Several media tags not supported, {0}continuing...", force ? "" : "not ");
                DicConsole.ErrorWriteLine("Several media tags not supported, {0}continuing...", force ? "" : "not ");
                if (!force)
                {
                    return;
                }
            }

            dumpLog.WriteLine("Reading {0} sectors at a time.", blocksToRead);
            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, dskType, 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;

            double           cmdDuration      = 0;
            uint             saveBlocksToRead = blocksToRead;
            DumpHardwareType currentTry       = null;
            ExtentsULong     extents          = null;

            ResumeSupport.Process(true, true, totalSize, dev.Manufacturer, dev.Model, dev.Serial, dev.PlatformId,
                                  ref resume, ref currentTry, ref extents);
            if (currentTry == null || extents == null)
            {
                throw new NotImplementedException("Could not process resume file, not continuing...");
            }

            outputPlugin.SetTracks(new List <Track>
            {
                new Track
                {
                    TrackBytesPerSector    = (int)BLOCK_SIZE,
                    TrackEndSector         = blocks - 1,
                    TrackSequence          = 1,
                    TrackRawBytesPerSector = (int)BLOCK_SIZE,
                    TrackSubchannelType    = TrackSubchannelType.None,
                    TrackSession           = 1,
                    TrackType = TrackType.Data
                }
            });

            ulong currentSector = resume.NextBlock;

            if (resume.NextBlock > 0)
            {
                dumpLog.WriteLine("Resuming from block {0}.", resume.NextBlock);
            }
            bool newTrim = false;

            dumpLog.WriteLine("Reading game partition.");
            for (int e = 0; e <= 16; e++)
            {
                if (aborted)
                {
                    resume.NextBlock   = currentSector;
                    currentTry.Extents = ExtentsConverter.ToMetadata(extents);
                    dumpLog.WriteLine("Aborted!");
                    break;
                }

                if (currentSector >= blocks)
                {
                    break;
                }

                ulong extentStart, extentEnd;
                // Extents
                if (e < 16)
                {
                    if (xboxSs.Value.Extents[e].StartPSN <= xboxSs.Value.Layer0EndPSN)
                    {
                        extentStart = xboxSs.Value.Extents[e].StartPSN - 0x30000;
                    }
                    else
                    {
                        extentStart = (xboxSs.Value.Layer0EndPSN + 1) * 2 -
                                      ((xboxSs.Value.Extents[e].StartPSN ^ 0xFFFFFF) + 1) - 0x30000;
                    }
                    if (xboxSs.Value.Extents[e].EndPSN <= xboxSs.Value.Layer0EndPSN)
                    {
                        extentEnd = xboxSs.Value.Extents[e].EndPSN - 0x30000;
                    }
                    else
                    {
                        extentEnd = (xboxSs.Value.Layer0EndPSN + 1) * 2 -
                                    ((xboxSs.Value.Extents[e].EndPSN ^ 0xFFFFFF) + 1) - 0x30000;
                    }
                }
                // After last extent
                else
                {
                    extentStart = blocks;
                    extentEnd   = blocks;
                }

                if (currentSector > extentEnd)
                {
                    continue;
                }

                for (ulong i = currentSector; i < extentStart; i += blocksToRead)
                {
                    saveBlocksToRead = blocksToRead;

                    if (aborted)
                    {
                        currentTry.Extents = ExtentsConverter.ToMetadata(extents);
                        dumpLog.WriteLine("Aborted!");
                        break;
                    }

                    if (extentStart - i < blocksToRead)
                    {
                        blocksToRead = (uint)(extentStart - i);
                    }

                    #pragma warning disable RECS0018 // Comparison of floating point numbers with equality operator
                    if (currentSpeed > maxSpeed && currentSpeed != 0)
                    {
                        maxSpeed = currentSpeed;
                    }
                    if (currentSpeed < minSpeed && currentSpeed != 0)
                    {
                        minSpeed = currentSpeed;
                    }
                    #pragma warning restore RECS0018 // Comparison of floating point numbers with equality operator

                    DicConsole.Write("\rReading sector {0} of {1} ({2:F3} MiB/sec.)", i, totalSize, currentSpeed);

                    sense = dev.Read12(out readBuffer, out senseBuf, 0, false, false, false, false, (uint)i, BLOCK_SIZE,
                                       0, blocksToRead, false, dev.Timeout, out cmdDuration);
                    totalDuration += cmdDuration;

                    if (!sense && !dev.Error)
                    {
                        mhddLog.Write(i, cmdDuration);
                        ibgLog.Write(i, currentSpeed * 1024);
                        DateTime writeStart = DateTime.Now;
                        outputPlugin.WriteSectors(readBuffer, i, blocksToRead);
                        imageWriteDuration += (DateTime.Now - writeStart).TotalSeconds;
                        extents.Add(i, blocksToRead, true);
                    }
                    else
                    {
                        // TODO: Reset device after X errors
                        if (stopOnError)
                        {
                            return;             // TODO: Return more cleanly
                        }
                        if (i + skip > blocks)
                        {
                            skip = (uint)(blocks - i);
                        }

                        // Write empty data
                        DateTime writeStart = DateTime.Now;
                        outputPlugin.WriteSectors(new byte[BLOCK_SIZE * skip], i, skip);
                        imageWriteDuration += (DateTime.Now - writeStart).TotalSeconds;

                        for (ulong b = i; b < i + skip; b++)
                        {
                            resume.BadBlocks.Add(b);
                        }

                        DicConsole.DebugWriteLine("Dump-Media", "READ error:\n{0}", Sense.PrettifySense(senseBuf));
                        mhddLog.Write(i, cmdDuration < 500 ? 65535 : cmdDuration);

                        ibgLog.Write(i, 0);

                        dumpLog.WriteLine("Skipping {0} blocks from errored block {1}.", skip, i);
                        i += skip - blocksToRead;
                        string[] senseLines = Sense.PrettifySense(senseBuf).Split(new[] { Environment.NewLine },
                                                                                  StringSplitOptions
                                                                                  .RemoveEmptyEntries);
                        foreach (string senseLine in senseLines)
                        {
                            dumpLog.WriteLine(senseLine);
                        }
                        newTrim = true;
                    }

                    double newSpeed =
                        (double)BLOCK_SIZE * blocksToRead / 1048576 / (cmdDuration / 1000);
                    if (!double.IsInfinity(newSpeed))
                    {
                        currentSpeed = newSpeed;
                    }
                    blocksToRead     = saveBlocksToRead;
                    currentSector    = i + 1;
                    resume.NextBlock = currentSector;
                }

                for (ulong i = extentStart; i <= extentEnd; i += blocksToRead)
                {
                    saveBlocksToRead = blocksToRead;
                    if (aborted)
                    {
                        currentTry.Extents = ExtentsConverter.ToMetadata(extents);
                        dumpLog.WriteLine("Aborted!");
                        break;
                    }

                    if (extentEnd - i < blocksToRead)
                    {
                        blocksToRead = (uint)(extentEnd - i) + 1;
                    }

                    mhddLog.Write(i, cmdDuration);
                    ibgLog.Write(i, currentSpeed * 1024);
                    // Write empty data
                    DateTime writeStart = DateTime.Now;
                    outputPlugin.WriteSectors(new byte[BLOCK_SIZE * blocksToRead], i, blocksToRead);
                    imageWriteDuration += (DateTime.Now - writeStart).TotalSeconds;
                    blocksToRead        = saveBlocksToRead;
                    extents.Add(i, blocksToRead, true);
                    currentSector    = i + 1;
                    resume.NextBlock = currentSector;
                }

                if (!aborted)
                {
                    currentSector = extentEnd + 1;
                }
            }

            // Middle Zone D
            dumpLog.WriteLine("Writing Middle Zone D (empty).");
            for (ulong middle = currentSector - blocks - 1; middle < middleZone - 1; middle += blocksToRead)
            {
                if (aborted)
                {
                    currentTry.Extents = ExtentsConverter.ToMetadata(extents);
                    dumpLog.WriteLine("Aborted!");
                    break;
                }

                if (middleZone - 1 - middle < blocksToRead)
                {
                    blocksToRead = (uint)(middleZone - 1 - middle);
                }

                DicConsole.Write("\rReading sector {0} of {1} ({2:F3} MiB/sec.)", middle + currentSector, totalSize,
                                 currentSpeed);

                mhddLog.Write(middle + currentSector, cmdDuration);
                ibgLog.Write(middle + currentSector, currentSpeed * 1024);
                // Write empty data
                DateTime writeStart = DateTime.Now;
                outputPlugin.WriteSectors(new byte[BLOCK_SIZE * blocksToRead], middle + currentSector, blocksToRead);
                imageWriteDuration += (DateTime.Now - writeStart).TotalSeconds;
                extents.Add(currentSector, blocksToRead, true);

                currentSector   += blocksToRead;
                resume.NextBlock = currentSector;
            }

            blocksToRead = saveBlocksToRead;

            dumpLog.WriteLine("Locking drive.");
            sense = dev.KreonLock(out senseBuf, dev.Timeout, out _);
            if (sense)
            {
                dumpLog.WriteLine("Cannot lock drive, not continuing.");
                DicConsole.ErrorWriteLine("Cannot lock drive, not continuing.");
                return;
            }

            sense = dev.ReadCapacity(out readBuffer, out senseBuf, dev.Timeout, out _);
            if (sense)
            {
                DicConsole.ErrorWriteLine("Cannot get disc capacity.");
                return;
            }

            // Video Layer 1
            dumpLog.WriteLine("Reading Video Layer 1.");
            for (ulong l1 = currentSector - blocks - middleZone + l0Video; l1 < l0Video + l1Video; l1 += blocksToRead)
            {
                if (aborted)
                {
                    currentTry.Extents = ExtentsConverter.ToMetadata(extents);
                    dumpLog.WriteLine("Aborted!");
                    break;
                }

                if (l0Video + l1Video - l1 < blocksToRead)
                {
                    blocksToRead = (uint)(l0Video + l1Video - l1);
                }

                #pragma warning disable RECS0018 // Comparison of floating point numbers with equality operator
                if (currentSpeed > maxSpeed && currentSpeed != 0)
                {
                    maxSpeed = currentSpeed;
                }
                if (currentSpeed < minSpeed && currentSpeed != 0)
                {
                    minSpeed = currentSpeed;
                }
                #pragma warning restore RECS0018 // Comparison of floating point numbers with equality operator

                DicConsole.Write("\rReading sector {0} of {1} ({2:F3} MiB/sec.)", currentSector, totalSize,
                                 currentSpeed);

                sense = dev.Read12(out readBuffer, out senseBuf, 0, false, false, false, false, (uint)l1, BLOCK_SIZE, 0,
                                   blocksToRead, false, dev.Timeout, out cmdDuration);
                totalDuration += cmdDuration;

                if (!sense && !dev.Error)
                {
                    mhddLog.Write(currentSector, cmdDuration);
                    ibgLog.Write(currentSector, currentSpeed * 1024);
                    DateTime writeStart = DateTime.Now;
                    outputPlugin.WriteSectors(readBuffer, currentSector, blocksToRead);
                    imageWriteDuration += (DateTime.Now - writeStart).TotalSeconds;
                    extents.Add(currentSector, blocksToRead, true);
                }
                else
                {
                    // TODO: Reset device after X errors
                    if (stopOnError)
                    {
                        return;             // TODO: Return more cleanly
                    }
                    // Write empty data
                    DateTime writeStart = DateTime.Now;
                    outputPlugin.WriteSectors(new byte[BLOCK_SIZE * skip], currentSector, skip);
                    imageWriteDuration += (DateTime.Now - writeStart).TotalSeconds;

                    // TODO: Handle errors in video partition
                    //errored += blocksToRead;
                    //resume.BadBlocks.Add(l1);
                    DicConsole.DebugWriteLine("Dump-Media", "READ error:\n{0}", Sense.PrettifySense(senseBuf));
                    mhddLog.Write(l1, cmdDuration < 500 ? 65535 : cmdDuration);

                    ibgLog.Write(l1, 0);
                    dumpLog.WriteLine("Skipping {0} blocks from errored block {1}.", skip, l1);
                    l1 += skip - blocksToRead;
                    string[] senseLines = Sense.PrettifySense(senseBuf).Split(new[] { Environment.NewLine },
                                                                              StringSplitOptions.RemoveEmptyEntries);
                    foreach (string senseLine in senseLines)
                    {
                        dumpLog.WriteLine(senseLine);
                    }
                }

                double newSpeed =
                    (double)BLOCK_SIZE * blocksToRead / 1048576 / (cmdDuration / 1000);
                if (!double.IsInfinity(newSpeed))
                {
                    currentSpeed = newSpeed;
                }
                currentSector   += blocksToRead;
                resume.NextBlock = currentSector;
            }

            dumpLog.WriteLine("Unlocking drive (Wxripper).");
            sense = dev.KreonUnlockWxripper(out senseBuf, dev.Timeout, out _);
            if (sense)
            {
                dumpLog.WriteLine("Cannot unlock drive, not continuing.");
                DicConsole.ErrorWriteLine("Cannot unlock drive, not continuing.");
                return;
            }

            sense = dev.ReadCapacity(out readBuffer, out senseBuf, dev.Timeout, out _);
            if (sense)
            {
                DicConsole.ErrorWriteLine("Cannot get disc capacity.");
                return;
            }

            end = DateTime.UtcNow;
            DicConsole.WriteLine();
            mhddLog.Close();
            ibgLog.Close(dev, blocks, BLOCK_SIZE, (end - start).TotalSeconds, currentSpeed * 1024,
                         BLOCK_SIZE * (double)(blocks + 1) / 1024 / (totalDuration / 1000),
                         devicePath);
            dumpLog.WriteLine("Dump finished in {0} seconds.", (end - start).TotalSeconds);
            dumpLog.WriteLine("Average dump speed {0:F3} KiB/sec.",
                              (double)BLOCK_SIZE * (double)(blocks + 1) / 1024 / (totalDuration / 1000));
            dumpLog.WriteLine("Average write speed {0:F3} KiB/sec.",
                              (double)BLOCK_SIZE * (double)(blocks + 1) / 1024 / imageWriteDuration);

            #region Trimming
            if (resume.BadBlocks.Count > 0 && !aborted && !notrim && newTrim)
            {
                start = DateTime.UtcNow;
                dumpLog.WriteLine("Trimming bad sectors");

                ulong[] tmpArray = resume.BadBlocks.ToArray();
                foreach (ulong badSector in tmpArray)
                {
                    if (aborted)
                    {
                        currentTry.Extents = ExtentsConverter.ToMetadata(extents);
                        dumpLog.WriteLine("Aborted!");
                        break;
                    }

                    DicConsole.Write("\rTrimming sector {0}", badSector);

                    sense = dev.Read12(out readBuffer, out senseBuf, 0, false, false, false, false, (uint)badSector,
                                       BLOCK_SIZE, 0, 1, false, dev.Timeout, out cmdDuration);
                    totalDuration += cmdDuration;

                    if (sense || dev.Error)
                    {
                        continue;
                    }

                    resume.BadBlocks.Remove(badSector);
                    extents.Add(badSector);
                    outputPlugin.WriteSector(readBuffer, badSector);
                }

                DicConsole.WriteLine();
                end = DateTime.UtcNow;
                dumpLog.WriteLine("Trimmming finished in {0} seconds.", (end - start).TotalSeconds);
            }
            #endregion Trimming

            #region Error handling
            if (resume.BadBlocks.Count > 0 && !aborted && retryPasses > 0)
            {
                List <ulong> tmpList = new List <ulong>();

                foreach (ulong ur in resume.BadBlocks)
                {
                    for (ulong i = ur; i < ur + blocksToRead; i++)
                    {
                        tmpList.Add(i);
                    }
                }

                tmpList.Sort();

                int  pass              = 1;
                bool forward           = true;
                bool runningPersistent = false;

                resume.BadBlocks = tmpList;
                Modes.ModePage?currentModePage = null;
                byte[]         md6;
                byte[]         md10;

                if (persistent)
                {
                    Modes.ModePage_01_MMC pgMmc;

                    sense = dev.ModeSense6(out readBuffer, out _, false, ScsiModeSensePageControl.Current, 0x01,
                                           dev.Timeout, out _);
                    if (sense)
                    {
                        sense = dev.ModeSense10(out readBuffer, out _, false, ScsiModeSensePageControl.Current, 0x01,
                                                dev.Timeout, out _);

                        if (!sense)
                        {
                            Modes.DecodedMode?dcMode10 =
                                Modes.DecodeMode10(readBuffer, PeripheralDeviceTypes.MultiMediaDevice);

                            if (dcMode10.HasValue)
                            {
                                foreach (Modes.ModePage modePage in dcMode10.Value.Pages)
                                {
                                    if (modePage.Page == 0x01 && modePage.Subpage == 0x00)
                                    {
                                        currentModePage = modePage;
                                    }
                                }
                            }
                        }
                    }
                    else
                    {
                        Modes.DecodedMode?dcMode6 =
                            Modes.DecodeMode6(readBuffer, PeripheralDeviceTypes.MultiMediaDevice);

                        if (dcMode6.HasValue)
                        {
                            foreach (Modes.ModePage modePage in dcMode6.Value.Pages)
                            {
                                if (modePage.Page == 0x01 && modePage.Subpage == 0x00)
                                {
                                    currentModePage = modePage;
                                }
                            }
                        }
                    }

                    if (currentModePage == null)
                    {
                        pgMmc = new Modes.ModePage_01_MMC {
                            PS = false, ReadRetryCount = 0x20, Parameter = 0x00
                        };
                        currentModePage = new Modes.ModePage
                        {
                            Page         = 0x01,
                            Subpage      = 0x00,
                            PageResponse = Modes.EncodeModePage_01_MMC(pgMmc)
                        };
                    }

                    pgMmc = new Modes.ModePage_01_MMC {
                        PS = false, ReadRetryCount = 255, Parameter = 0x20
                    };
                    Modes.DecodedMode md = new Modes.DecodedMode
                    {
                        Header = new Modes.ModeHeader(),
                        Pages  = new[]
                        {
                            new Modes.ModePage
                            {
                                Page         = 0x01,
                                Subpage      = 0x00,
                                PageResponse = Modes.EncodeModePage_01_MMC(pgMmc)
                            }
                        }
                    };
                    md6  = Modes.EncodeMode6(md, dev.ScsiType);
                    md10 = Modes.EncodeMode10(md, dev.ScsiType);

                    dumpLog.WriteLine("Sending MODE SELECT to drive (return damaged blocks).");
                    sense = dev.ModeSelect(md6, out senseBuf, true, false, dev.Timeout, out _);
                    if (sense)
                    {
                        sense = dev.ModeSelect10(md10, out senseBuf, true, false, dev.Timeout, out _);
                    }

                    if (sense)
                    {
                        DicConsole
                        .WriteLine("Drive did not accept MODE SELECT command for persistent error reading, try another drive.");
                        DicConsole.DebugWriteLine("Error: {0}", Sense.PrettifySense(senseBuf));
                        dumpLog.WriteLine("Drive did not accept MODE SELECT command for persistent error reading, try another drive.");
                    }
                    else
                    {
                        runningPersistent = true;
                    }
                }

repeatRetry:
                ulong[] tmpArray = resume.BadBlocks.ToArray();
                foreach (ulong badSector in tmpArray)
                {
                    if (aborted)
                    {
                        currentTry.Extents = ExtentsConverter.ToMetadata(extents);
                        dumpLog.WriteLine("Aborted!");
                        break;
                    }

                    DicConsole.Write("\rRetrying sector {0}, pass {1}, {3}{2}", badSector, pass,
                                     forward ? "forward" : "reverse",
                                     runningPersistent ? "recovering partial data, " : "");

                    sense = dev.Read12(out readBuffer, out senseBuf, 0, false, false, false, false, (uint)badSector,
                                       BLOCK_SIZE, 0, 1, false, dev.Timeout, out cmdDuration);
                    totalDuration += cmdDuration;

                    if (!sense && !dev.Error)
                    {
                        resume.BadBlocks.Remove(badSector);
                        extents.Add(badSector);
                        outputPlugin.WriteSector(readBuffer, badSector);
                        dumpLog.WriteLine("Correctly retried block {0} in pass {1}.", badSector, pass);
                    }
                    else if (runningPersistent)
                    {
                        outputPlugin.WriteSector(readBuffer, badSector);
                    }
                }

                if (pass < retryPasses && !aborted && resume.BadBlocks.Count > 0)
                {
                    pass++;
                    forward = !forward;
                    resume.BadBlocks.Sort();
                    resume.BadBlocks.Reverse();
                    goto repeatRetry;
                }

                if (runningPersistent && currentModePage.HasValue)
                {
                    Modes.DecodedMode md = new Modes.DecodedMode
                    {
                        Header = new Modes.ModeHeader(),
                        Pages  = new[] { currentModePage.Value }
                    };
                    md6  = Modes.EncodeMode6(md, dev.ScsiType);
                    md10 = Modes.EncodeMode10(md, dev.ScsiType);

                    dumpLog.WriteLine("Sending MODE SELECT to drive (return device to previous status).");
                    sense = dev.ModeSelect(md6, out senseBuf, true, false, dev.Timeout, out _);
                    if (sense)
                    {
                        dev.ModeSelect10(md10, out senseBuf, true, false, dev.Timeout, out _);
                    }
                }

                DicConsole.WriteLine();
            }
            #endregion Error handling

            resume.BadBlocks.Sort();
            currentTry.Extents = ExtentsConverter.ToMetadata(extents);

            foreach (KeyValuePair <MediaTagType, byte[]> tag in mediaTags)
            {
                ret = outputPlugin.WriteMediaTag(tag.Value, tag.Key);
                if (ret || force)
                {
                    continue;
                }

                // Cannot write tag to image
                dumpLog.WriteLine($"Cannot write tag {tag.Key}.");
                throw new ArgumentException(outputPlugin.ErrorMessage);
            }

            resume.BadBlocks.Sort();
            foreach (ulong bad in resume.BadBlocks)
            {
                dumpLog.WriteLine("Sector {0} could not be read.", bad);
            }
            currentTry.Extents = ExtentsConverter.ToMetadata(extents);

            outputPlugin.SetDumpHardware(resume.Tries);
            if (preSidecar != null)
            {
                outputPlugin.SetCicmMetadata(preSidecar);
            }
            dumpLog.WriteLine("Closing output file.");
            DicConsole.WriteLine("Closing output file.");
            DateTime closeStart = DateTime.Now;
            outputPlugin.Close();
            DateTime closeEnd = DateTime.Now;
            dumpLog.WriteLine("Closed in {0} seconds.", (closeEnd - closeStart).TotalSeconds);

            if (aborted)
            {
                dumpLog.WriteLine("Aborted!");
                return;
            }

            double totalChkDuration = 0;
            if (!nometadata)
            {
                dumpLog.WriteLine("Creating sidecar.");
                FiltersList filters     = new FiltersList();
                IFilter     filter      = filters.GetFilter(outputPath);
                IMediaImage inputPlugin = ImageFormat.Detect(filter);
                if (!inputPlugin.Open(filter))
                {
                    throw new ArgumentException("Could not open created image.");
                }

                DateTime         chkStart = DateTime.UtcNow;
                CICMMetadataType sidecar  = Sidecar.Create(inputPlugin, outputPath, filter.Id, encoding);
                end = DateTime.UtcNow;

                if (preSidecar != null)
                {
                    preSidecar.OpticalDisc = sidecar.OpticalDisc;
                    sidecar = preSidecar;
                }

                totalChkDuration = (end - chkStart).TotalMilliseconds;
                dumpLog.WriteLine("Sidecar created in {0} seconds.", (end - chkStart).TotalSeconds);
                dumpLog.WriteLine("Average checksum speed {0:F3} KiB/sec.",
                                  (double)BLOCK_SIZE * (double)(blocks + 1) / 1024 / (totalChkDuration / 1000));

                foreach (KeyValuePair <MediaTagType, byte[]> tag in mediaTags)
                {
                    Mmc.AddMediaTagToSidecar(outputPath, tag, ref sidecar);
                }

                List <(ulong start, string type)> filesystems = new List <(ulong start, string type)>();
                if (sidecar.OpticalDisc[0].Track != null)
                {
                    filesystems.AddRange(from xmlTrack in sidecar.OpticalDisc[0].Track
                                         where xmlTrack.FileSystemInformation != null
                                         from partition in xmlTrack.FileSystemInformation
                                         where partition.FileSystems != null
                                         from fileSystem in partition.FileSystems
                                         select((ulong)partition.StartSector, fileSystem.Type));
                }

                if (filesystems.Count > 0)
                {
                    foreach (var filesystem in filesystems.Select(o => new { o.start, o.type }).Distinct())
                    {
                        dumpLog.WriteLine("Found filesystem {0} at sector {1}", filesystem.type, filesystem.start);
                    }
                }

                sidecar.OpticalDisc[0].Layers = new LayersType
                {
                    type          = LayersTypeType.OTP,
                    typeSpecified = true,
                    Sectors       = new SectorsType[1]
                };
                sidecar.OpticalDisc[0].Layers.Sectors[0] = new SectorsType {
                    Value = (long)layerBreak
                };
                sidecar.OpticalDisc[0].Sessions   = 1;
                sidecar.OpticalDisc[0].Dimensions = Dimensions.DimensionsFromMediaType(dskType);
                CommonTypes.Metadata.MediaType.MediaTypeToString(dskType, out string xmlDskTyp,
                                                                 out string xmlDskSubTyp);
                sidecar.OpticalDisc[0].DiscType    = xmlDskTyp;
                sidecar.OpticalDisc[0].DiscSubType = xmlDskSubTyp;

                foreach (KeyValuePair <MediaTagType, byte[]> tag in mediaTags)
                {
                    if (outputPlugin.SupportedMediaTags.Contains(tag.Key))
                    {
                        Mmc.AddMediaTagToSidecar(outputPath, tag, ref sidecar);
                    }
                }

                DicConsole.WriteLine("Writing metadata sidecar");

                FileStream xmlFs = new FileStream(outputPrefix + ".cicm.xml", FileMode.Create);

                XmlSerializer xmlSer = new XmlSerializer(typeof(CICMMetadataType));
                xmlSer.Serialize(xmlFs, sidecar);
                xmlFs.Close();
            }

            DicConsole.WriteLine();
            DicConsole.WriteLine("Took a total of {0:F3} seconds ({1:F3} processing commands, {2:F3} checksumming, {3:F3} writing, {4:F3} closing).",
                                 (end - start).TotalSeconds, totalDuration / 1000,
                                 totalChkDuration / 1000,
                                 imageWriteDuration, (closeEnd - closeStart).TotalSeconds);
            DicConsole.WriteLine("Avegare speed: {0:F3} MiB/sec.",
                                 (double)BLOCK_SIZE * (double)(blocks + 1) / 1048576 / (totalDuration / 1000));
            DicConsole.WriteLine("Fastest speed burst: {0:F3} MiB/sec.", maxSpeed);
            DicConsole.WriteLine("Slowest speed burst: {0:F3} MiB/sec.", minSpeed);
            DicConsole.WriteLine("{0} sectors could not be read.", resume.BadBlocks.Count);
            DicConsole.WriteLine();

            Statistics.AddMedia(dskType, true);
        }
Exemple #4
0
        /// <summary>
        ///     Dumps the tape from a SCSI Streaming device
        /// </summary>
        /// <param name="dev">Device</param>
        /// <param name="devicePath">Path to the device</param>
        /// <param name="outputPrefix">Prefix for output data files</param>
        /// <param name="resume">Information for dump resuming</param>
        /// <param name="dumpLog">Dump logger</param>
        internal static void Dump(Device dev, string outputPrefix, string devicePath,
                                  ref Resume resume,
                                  ref DumpLog dumpLog, CICMMetadataType preSidecar)
        {
            FixedSense?      fxSense;
            bool             aborted;
            bool             sense;
            ulong            blocks = 0;
            uint             blockSize;
            MediaType        dskType = MediaType.Unknown;
            DateTime         start;
            DateTime         end;
            double           totalDuration    = 0;
            double           totalChkDuration = 0;
            double           currentSpeed     = 0;
            double           maxSpeed         = double.MinValue;
            double           minSpeed         = double.MaxValue;
            CICMMetadataType sidecar          = preSidecar ?? new CICMMetadataType();

            dev.RequestSense(out byte[] senseBuf, dev.Timeout, out double duration);
            fxSense = Sense.DecodeFixed(senseBuf, out string strSense);

            if (fxSense.HasValue && fxSense.Value.SenseKey != SenseKeys.NoSense)
            {
                dumpLog.WriteLine("Device not ready. Sense {0}h ASC {1:X2}h ASCQ {2:X2}h", fxSense.Value.SenseKey,
                                  fxSense.Value.ASC, fxSense.Value.ASCQ);
                DicConsole.ErrorWriteLine("Drive has status error, please correct. Sense follows...");
                DicConsole.ErrorWriteLine("{0}", strSense);
                return;
            }

            // Not in BOM/P
            if (fxSense.HasValue && fxSense.Value.ASC == 0x00 && fxSense.Value.ASCQ != 0x00 &&
                fxSense.Value.ASCQ != 0x04 && fxSense.Value.SenseKey != SenseKeys.IllegalRequest)
            {
                dumpLog.WriteLine("Rewinding, please wait...");
                DicConsole.Write("Rewinding, please wait...");
                // Rewind, let timeout apply
                dev.Rewind(out senseBuf, dev.Timeout, out duration);

                // Still rewinding?
                // TODO: Pause?
                do
                {
                    DicConsole.Write("\rRewinding, please wait...");
                    dev.RequestSense(out senseBuf, dev.Timeout, out duration);
                    fxSense = Sense.DecodeFixed(senseBuf, out strSense);
                }while(fxSense.HasValue && fxSense.Value.ASC == 0x00 &&
                       (fxSense.Value.ASCQ == 0x1A || fxSense.Value.ASCQ != 0x04));

                dev.RequestSense(out senseBuf, dev.Timeout, out duration);
                fxSense = Sense.DecodeFixed(senseBuf, out strSense);

                // And yet, did not rewind!
                if (fxSense.HasValue &&
                    (fxSense.Value.ASC == 0x00 && fxSense.Value.ASCQ != 0x04 || fxSense.Value.ASC != 0x00))
                {
                    DicConsole.WriteLine();
                    DicConsole.ErrorWriteLine("Drive could not rewind, please correct. Sense follows...");
                    DicConsole.ErrorWriteLine("{0}", strSense);
                    dumpLog.WriteLine("Drive could not rewind, please correct. Sense follows...");
                    dumpLog.WriteLine("Device not ready. Sense {0}h ASC {1:X2}h ASCQ {2:X2}h", fxSense.Value.SenseKey,
                                      fxSense.Value.ASC, fxSense.Value.ASCQ);
                    return;
                }

                DicConsole.WriteLine();
            }

            // Check position
            sense = dev.ReadPosition(out byte[] cmdBuf, out senseBuf, SscPositionForms.Short, dev.Timeout,
                                     out duration);

            if (sense)
            {
                // READ POSITION is mandatory starting SCSI-2, so do not cry if the drive does not recognize the command (SCSI-1 or earlier)
                // Anyway, <=SCSI-1 tapes do not support partitions
                fxSense = Sense.DecodeFixed(senseBuf, out strSense);

                if (fxSense.HasValue && (fxSense.Value.ASC == 0x20 && fxSense.Value.ASCQ != 0x00 ||
                                         fxSense.Value.ASC != 0x20 &&
                                         fxSense.Value.SenseKey != SenseKeys.IllegalRequest))
                {
                    DicConsole.ErrorWriteLine("Could not get position. Sense follows...");
                    DicConsole.ErrorWriteLine("{0}", strSense);
                    dumpLog.WriteLine("Could not get position. Sense follows...");
                    dumpLog.WriteLine("Device not ready. Sense {0}h ASC {1:X2}h ASCQ {2:X2}h", fxSense.Value.SenseKey,
                                      fxSense.Value.ASC, fxSense.Value.ASCQ);
                    return;
                }
            }
            else
            {
                // Not in partition 0
                if (cmdBuf[1] != 0)
                {
                    DicConsole.Write("Drive not in partition 0. Rewinding, please wait...");
                    dumpLog.WriteLine("Drive not in partition 0. Rewinding, please wait...");
                    // Rewind, let timeout apply
                    sense = dev.Locate(out senseBuf, false, 0, 0, dev.Timeout, out duration);
                    if (sense)
                    {
                        DicConsole.WriteLine();
                        DicConsole.ErrorWriteLine("Drive could not rewind, please correct. Sense follows...");
                        DicConsole.ErrorWriteLine("{0}", strSense);
                        dumpLog.WriteLine("Drive could not rewind, please correct. Sense follows...");
                        dumpLog.WriteLine("Device not ready. Sense {0}h ASC {1:X2}h ASCQ {2:X2}h",
                                          fxSense.Value.SenseKey, fxSense.Value.ASC, fxSense.Value.ASCQ);
                        return;
                    }

                    // Still rewinding?
                    // TODO: Pause?
                    do
                    {
                        Thread.Sleep(1000);
                        DicConsole.Write("\rRewinding, please wait...");
                        dev.RequestSense(out senseBuf, dev.Timeout, out duration);
                        fxSense = Sense.DecodeFixed(senseBuf, out strSense);
                    }while(fxSense.HasValue && fxSense.Value.ASC == 0x00 &&
                           (fxSense.Value.ASCQ == 0x1A || fxSense.Value.ASCQ == 0x19));

                    // And yet, did not rewind!
                    if (fxSense.HasValue && (fxSense.Value.ASC == 0x00 && fxSense.Value.ASCQ != 0x04 ||
                                             fxSense.Value.ASC != 0x00))
                    {
                        DicConsole.WriteLine();
                        DicConsole.ErrorWriteLine("Drive could not rewind, please correct. Sense follows...");
                        DicConsole.ErrorWriteLine("{0}", strSense);
                        dumpLog.WriteLine("Drive could not rewind, please correct. Sense follows...");
                        dumpLog.WriteLine("Device not ready. Sense {0}h ASC {1:X2}h ASCQ {2:X2}h",
                                          fxSense.Value.SenseKey, fxSense.Value.ASC, fxSense.Value.ASCQ);
                        return;
                    }

                    sense = dev.ReadPosition(out cmdBuf, out senseBuf, SscPositionForms.Short, dev.Timeout,
                                             out duration);
                    if (sense)
                    {
                        fxSense = Sense.DecodeFixed(senseBuf, out strSense);
                        DicConsole.ErrorWriteLine("Drive could not rewind, please correct. Sense follows...");
                        DicConsole.ErrorWriteLine("{0}", strSense);
                        dumpLog.WriteLine("Drive could not rewind, please correct. Sense follows...");
                        dumpLog.WriteLine("Device not ready. Sense {0}h ASC {1:X2}h ASCQ {2:X2}h",
                                          fxSense.Value.SenseKey, fxSense.Value.ASC, fxSense.Value.ASCQ);
                        return;
                    }

                    // Still not in partition 0!!!?
                    if (cmdBuf[1] != 0)
                    {
                        DicConsole.ErrorWriteLine("Drive could not rewind to partition 0 but no error occurred...");
                        dumpLog.WriteLine("Drive could not rewind to partition 0 but no error occurred...");
                        return;
                    }

                    DicConsole.WriteLine();
                }
            }

            sidecar.BlockMedia    = new BlockMediaType[1];
            sidecar.BlockMedia[0] = new BlockMediaType {
                SCSI = new SCSIType()
            };
            byte scsiMediumTypeTape  = 0;
            byte scsiDensityCodeTape = 0;

            dumpLog.WriteLine("Requesting MODE SENSE (10).");
            sense = dev.ModeSense10(out cmdBuf, out senseBuf, false, true, ScsiModeSensePageControl.Current, 0x3F, 0xFF,
                                    5, out duration);
            if (!sense || dev.Error)
            {
                sense = dev.ModeSense10(out cmdBuf, out senseBuf, false, true, ScsiModeSensePageControl.Current, 0x3F,
                                        0x00, 5, out duration);
            }

            Modes.DecodedMode?decMode = null;

            if (!sense && !dev.Error)
            {
                if (Modes.DecodeMode10(cmdBuf, dev.ScsiType).HasValue)
                {
                    decMode = Modes.DecodeMode10(cmdBuf, dev.ScsiType);
                    sidecar.BlockMedia[0].SCSI.ModeSense10 = new DumpType
                    {
                        Image     = outputPrefix + ".modesense10.bin",
                        Size      = cmdBuf.Length,
                        Checksums = Checksum.GetChecksums(cmdBuf).ToArray()
                    };
                    DataFile.WriteTo("SCSI Dump", sidecar.BlockMedia[0].SCSI.ModeSense10.Image, cmdBuf);
                }
            }

            dumpLog.WriteLine("Requesting MODE SENSE (6).");
            sense = dev.ModeSense6(out cmdBuf, out senseBuf, false, ScsiModeSensePageControl.Current, 0x3F, 0x00, 5,
                                   out duration);
            if (sense || dev.Error)
            {
                sense = dev.ModeSense6(out cmdBuf, out senseBuf, false, ScsiModeSensePageControl.Current, 0x3F, 0x00, 5,
                                       out duration);
            }
            if (sense || dev.Error)
            {
                sense = dev.ModeSense(out cmdBuf, out senseBuf, 5, out duration);
            }

            if (!sense && !dev.Error)
            {
                if (Modes.DecodeMode6(cmdBuf, dev.ScsiType).HasValue)
                {
                    decMode = Modes.DecodeMode6(cmdBuf, dev.ScsiType);
                    sidecar.BlockMedia[0].SCSI.ModeSense = new DumpType
                    {
                        Image     = outputPrefix + ".modesense.bin",
                        Size      = cmdBuf.Length,
                        Checksums = Checksum.GetChecksums(cmdBuf).ToArray()
                    };
                    DataFile.WriteTo("SCSI Dump", sidecar.BlockMedia[0].SCSI.ModeSense.Image, cmdBuf);
                }
            }

            // TODO: Check partitions page
            if (decMode.HasValue)
            {
                scsiMediumTypeTape = (byte)decMode.Value.Header.MediumType;
                if (decMode.Value.Header.BlockDescriptors != null && decMode.Value.Header.BlockDescriptors.Length >= 1)
                {
                    scsiDensityCodeTape = (byte)decMode.Value.Header.BlockDescriptors[0].Density;
                }
                blockSize = decMode.Value.Header.BlockDescriptors[0].BlockLength;
                dumpLog.WriteLine("Device reports {0} blocks ({1} bytes).", blocks, blocks * blockSize);
            }
            else
            {
                blockSize = 1;
            }

            if (dskType == MediaType.Unknown)
            {
                dskType = MediaTypeFromScsi.Get((byte)dev.ScsiType, dev.Manufacturer, dev.Model, scsiMediumTypeTape,
                                                scsiDensityCodeTape, blocks, blockSize);
            }

            DicConsole.WriteLine("Media identified as {0}", dskType);

            dumpLog.WriteLine("SCSI device type: {0}.", dev.ScsiType);
            dumpLog.WriteLine("SCSI medium type: {0}.", scsiMediumTypeTape);
            dumpLog.WriteLine("SCSI density type: {0}.", scsiDensityCodeTape);
            dumpLog.WriteLine("Media identified as {0}.", dskType);

            bool  endOfMedia           = false;
            ulong currentBlock         = 0;
            ulong currentFile          = 0;
            byte  currentPartition     = 0;
            byte  totalPartitions      = 1; // TODO: Handle partitions.
            ulong currentSize          = 0;
            ulong currentPartitionSize = 0;
            ulong currentFileSize      = 0;

            bool fixedLen    = false;
            uint transferLen = blockSize;

            sense = dev.Read6(out cmdBuf, out senseBuf, false, fixedLen, transferLen, blockSize, dev.Timeout,
                              out duration);
            if (sense)
            {
                fxSense = Sense.DecodeFixed(senseBuf, out strSense);
                if (fxSense.HasValue)
                {
                    if (fxSense.Value.SenseKey == SenseKeys.IllegalRequest)
                    {
                        sense = dev.Space(out senseBuf, SscSpaceCodes.LogicalBlock, -1, dev.Timeout, out duration);
                        if (sense)
                        {
                            fxSense = Sense.DecodeFixed(senseBuf, out strSense);
                            if (!fxSense.HasValue || !fxSense.Value.EOM)
                            {
                                DicConsole.WriteLine();
                                DicConsole.ErrorWriteLine("Drive could not return back. Sense follows...");
                                DicConsole.ErrorWriteLine("{0}", strSense);
                                dumpLog.WriteLine("Drive could not return back. Sense follows...");
                                dumpLog.WriteLine("Device not ready. Sense {0}h ASC {1:X2}h ASCQ {2:X2}h",
                                                  fxSense.Value.SenseKey, fxSense.Value.ASC, fxSense.Value.ASCQ);
                                return;
                            }
                        }

                        fixedLen    = true;
                        transferLen = 1;
                        sense       = dev.Read6(out cmdBuf, out senseBuf, false, fixedLen, transferLen, blockSize,
                                                dev.Timeout, out duration);
                        if (sense)
                        {
                            DicConsole.WriteLine();
                            DicConsole.ErrorWriteLine("Drive could not read. Sense follows...");
                            DicConsole.ErrorWriteLine("{0}", strSense);
                            dumpLog.WriteLine("Drive could not read. Sense follows...");
                            dumpLog.WriteLine("Device not ready. Sense {0}h ASC {1:X2}h ASCQ {2:X2}h",
                                              fxSense.Value.SenseKey, fxSense.Value.ASC, fxSense.Value.ASCQ);
                            return;
                        }
                    }
                    else
                    {
                        DicConsole.WriteLine();
                        DicConsole.ErrorWriteLine("Drive could not read. Sense follows...");
                        DicConsole.ErrorWriteLine("{0}", strSense);
                        dumpLog.WriteLine("Drive could not read. Sense follows...");
                        dumpLog.WriteLine("Device not ready. Sense {0}h ASC {1:X2}h ASCQ {2:X2}h",
                                          fxSense.Value.SenseKey, fxSense.Value.ASC, fxSense.Value.ASCQ);
                        return;
                    }
                }
                else
                {
                    DicConsole.WriteLine();
                    DicConsole.ErrorWriteLine("Cannot read device, don't know why, exiting...");
                    dumpLog.WriteLine("Cannot read device, don't know why, exiting...");
                    return;
                }
            }

            sense = dev.Space(out senseBuf, SscSpaceCodes.LogicalBlock, -1, dev.Timeout, out duration);
            if (sense)
            {
                fxSense = Sense.DecodeFixed(senseBuf, out strSense);
                if (!fxSense.HasValue || !fxSense.Value.EOM)
                {
                    DicConsole.WriteLine();
                    DicConsole.ErrorWriteLine("Drive could not return back. Sense follows...");
                    DicConsole.ErrorWriteLine("{0}", strSense);
                    dumpLog.WriteLine("Drive could not return back. Sense follows...");
                    dumpLog.WriteLine("Device not ready. Sense {0}h ASC {1:X2}h ASCQ {2:X2}h", fxSense.Value.SenseKey,
                                      fxSense.Value.ASC, fxSense.Value.ASCQ);
                    return;
                }
            }

            List <TapePartitionType> partitions = new List <TapePartitionType>();
            List <TapeFileType>      files      = new List <TapeFileType>();

            DicConsole.WriteLine();
            DataFile dumpFile = new DataFile(outputPrefix + ".bin");
            Checksum dataChk  = new Checksum();

            start = DateTime.UtcNow;
            MhddLog mhddLog = new MhddLog(outputPrefix + ".mhddlog.bin", dev, blocks, blockSize, 1);
            IbgLog  ibgLog  = new IbgLog(outputPrefix + ".ibg", 0x0008);

            TapeFileType currentTapeFile = new TapeFileType
            {
                Image = new ImageType
                {
                    format          = "BINARY",
                    offset          = (long)currentSize,
                    offsetSpecified = true,
                    Value           = outputPrefix + ".bin"
                },
                Sequence   = (long)currentFile,
                StartBlock = (long)currentBlock,
                BlockSize  = blockSize
            };
            Checksum          fileChk = new Checksum();
            TapePartitionType currentTapePartition = new TapePartitionType
            {
                Image = new ImageType
                {
                    format          = "BINARY",
                    offset          = (long)currentSize,
                    offsetSpecified = true,
                    Value           = outputPrefix + ".bin"
                },
                Sequence   = currentPartition,
                StartBlock = (long)currentBlock
            };
            Checksum partitionChk = new Checksum();

            aborted = false;
            System.Console.CancelKeyPress += (sender, e) => e.Cancel = aborted = true;

            while (currentPartition < totalPartitions)
            {
                if (aborted)
                {
                    dumpLog.WriteLine("Aborted!");
                    break;
                }

                if (endOfMedia)
                {
                    DicConsole.WriteLine();
                    DicConsole.WriteLine("Finished partition {0}", currentPartition);
                    dumpLog.WriteLine("Finished partition {0}", currentPartition);
                    currentTapePartition.File      = files.ToArray();
                    currentTapePartition.Checksums = partitionChk.End().ToArray();
                    currentTapePartition.EndBlock  = (long)(currentBlock - 1);
                    currentTapePartition.Size      = (long)currentPartitionSize;
                    partitions.Add(currentTapePartition);

                    currentPartition++;

                    if (currentPartition < totalPartitions)
                    {
                        currentFile++;
                        currentTapeFile = new TapeFileType
                        {
                            Image = new ImageType
                            {
                                format          = "BINARY",
                                offset          = (long)currentSize,
                                offsetSpecified = true,
                                Value           = outputPrefix + ".bin"
                            },
                            Sequence   = (long)currentFile,
                            StartBlock = (long)currentBlock,
                            BlockSize  = blockSize
                        };
                        currentFileSize      = 0;
                        fileChk              = new Checksum();
                        files                = new List <TapeFileType>();
                        currentTapePartition = new TapePartitionType
                        {
                            Image = new ImageType
                            {
                                format          = "BINARY",
                                offset          = (long)currentSize,
                                offsetSpecified = true,
                                Value           = outputPrefix + ".bin"
                            },
                            Sequence   = currentPartition,
                            StartBlock = (long)currentBlock
                        };
                        currentPartitionSize = 0;
                        partitionChk         = new Checksum();
                        DicConsole.WriteLine("Seeking to partition {0}", currentPartition);
                        dev.Locate(out senseBuf, false, currentPartition, 0, dev.Timeout, out duration);
                        totalDuration += duration;
                    }

                    continue;
                }

                #pragma warning disable RECS0018 // Comparison of floating point numbers with equality operator
                if (currentSpeed > maxSpeed && currentSpeed != 0)
                {
                    maxSpeed = currentSpeed;
                }
                if (currentSpeed < minSpeed && currentSpeed != 0)
                {
                    minSpeed = currentSpeed;
                }
                #pragma warning restore RECS0018 // Comparison of floating point numbers with equality operator

                DicConsole.Write("\rReading block {0} ({1:F3} MiB/sec.)", currentBlock, currentSpeed);

                sense = dev.Read6(out cmdBuf, out senseBuf, false, fixedLen, transferLen, blockSize, dev.Timeout,
                                  out duration);
                totalDuration += duration;

                if (sense)
                {
                    fxSense = Sense.DecodeFixed(senseBuf, out strSense);
                    if (fxSense.Value.ASC == 0x00 && fxSense.Value.ASCQ == 0x00 && fxSense.Value.ILI &&
                        fxSense.Value.InformationValid)
                    {
                        blockSize = (uint)((int)blockSize -
                                           BitConverter.ToInt32(BitConverter.GetBytes(fxSense.Value.Information), 0));
                        currentTapeFile.BlockSize = blockSize;

                        DicConsole.WriteLine();
                        DicConsole.WriteLine("Blocksize changed to {0} bytes at block {1}", blockSize, currentBlock);
                        dumpLog.WriteLine("Blocksize changed to {0} bytes at block {1}", blockSize, currentBlock);

                        sense = dev.Space(out senseBuf, SscSpaceCodes.LogicalBlock, -1, dev.Timeout,
                                          out duration);
                        totalDuration += duration;

                        if (sense)
                        {
                            fxSense = Sense.DecodeFixed(senseBuf, out strSense);
                            DicConsole.WriteLine();
                            DicConsole.ErrorWriteLine("Drive could not go back one block. Sense follows...");
                            DicConsole.ErrorWriteLine("{0}", strSense);
                            dumpFile.Close();
                            dumpLog.WriteLine("Drive could not go back one block. Sense follows...");
                            dumpLog.WriteLine("Device not ready. Sense {0}h ASC {1:X2}h ASCQ {2:X2}h",
                                              fxSense.Value.SenseKey, fxSense.Value.ASC, fxSense.Value.ASCQ);
                            return;
                        }

                        continue;
                    }

                    switch (fxSense.Value.SenseKey)
                    {
                    case SenseKeys.BlankCheck when currentBlock == 0:
                        DicConsole.WriteLine();
                        DicConsole.ErrorWriteLine("Cannot dump a blank tape...");
                        dumpFile.Close();
                        dumpLog.WriteLine("Cannot dump a blank tape...");
                        return;

                    // For sure this is an end-of-tape/partition
                    case SenseKeys.BlankCheck when fxSense.Value.ASC == 0x00 &&
                        (fxSense.Value.ASCQ == 0x02 || fxSense.Value.ASCQ == 0x05 ||
                         fxSense.Value.EOM):
                        // TODO: Detect end of partition
                        endOfMedia = true;
                        dumpLog.WriteLine("Found end-of-tape/partition...");
                        continue;

                    case SenseKeys.BlankCheck:
                        DicConsole.WriteLine();
                        DicConsole.WriteLine("Blank block found, end of tape?");
                        endOfMedia = true;
                        dumpLog.WriteLine("Blank block found, end of tape?...");
                        continue;
                    }

                    if ((fxSense.Value.SenseKey == SenseKeys.NoSense ||
                         fxSense.Value.SenseKey == SenseKeys.RecoveredError) &&
                        (fxSense.Value.ASCQ == 0x02 || fxSense.Value.ASCQ == 0x05 || fxSense.Value.EOM))
                    {
                        // TODO: Detect end of partition
                        endOfMedia = true;
                        dumpLog.WriteLine("Found end-of-tape/partition...");
                        continue;
                    }

                    if ((fxSense.Value.SenseKey == SenseKeys.NoSense ||
                         fxSense.Value.SenseKey == SenseKeys.RecoveredError) &&
                        (fxSense.Value.ASCQ == 0x01 || fxSense.Value.Filemark))
                    {
                        currentTapeFile.Checksums = fileChk.End().ToArray();
                        currentTapeFile.EndBlock  = (long)(currentBlock - 1);
                        currentTapeFile.Size      = (long)currentFileSize;
                        files.Add(currentTapeFile);

                        currentFile++;
                        currentTapeFile = new TapeFileType
                        {
                            Image = new ImageType
                            {
                                format          = "BINARY",
                                offset          = (long)currentSize,
                                offsetSpecified = true,
                                Value           = outputPrefix + ".bin"
                            },
                            Sequence   = (long)currentFile,
                            StartBlock = (long)currentBlock,
                            BlockSize  = blockSize
                        };
                        currentFileSize = 0;
                        fileChk         = new Checksum();

                        DicConsole.WriteLine();
                        DicConsole.WriteLine("Changed to file {0} at block {1}", currentFile, currentBlock);
                        dumpLog.WriteLine("Changed to file {0} at block {1}", currentFile, currentBlock);
                        continue;
                    }

                    // TODO: Add error recovering for tapes
                    fxSense = Sense.DecodeFixed(senseBuf, out strSense);
                    DicConsole.ErrorWriteLine("Drive could not read block. Sense follows...");
                    DicConsole.ErrorWriteLine("{0} {1}", fxSense.Value.SenseKey, strSense);
                    dumpLog.WriteLine("Drive could not read block. Sense follows...");
                    dumpLog.WriteLine("Device not ready. Sense {0}h ASC {1:X2}h ASCQ {2:X2}h", fxSense.Value.SenseKey,
                                      fxSense.Value.ASC, fxSense.Value.ASCQ);
                    return;
                }

                mhddLog.Write(currentBlock, duration);
                ibgLog.Write(currentBlock, currentSpeed * 1024);
                dumpFile.Write(cmdBuf);

                DateTime chkStart = DateTime.UtcNow;
                dataChk.Update(cmdBuf);
                fileChk.Update(cmdBuf);
                partitionChk.Update(cmdBuf);
                DateTime chkEnd      = DateTime.UtcNow;
                double   chkDuration = (chkEnd - chkStart).TotalMilliseconds;
                totalChkDuration += chkDuration;

                if (currentBlock % 10 == 0)
                {
                    double newSpeed = blockSize / (double)1048576 / (duration / 1000);
                    if (!double.IsInfinity(newSpeed))
                    {
                        currentSpeed = newSpeed;
                    }
                }

                currentBlock++;
                currentSize          += blockSize;
                currentFileSize      += blockSize;
                currentPartitionSize += blockSize;
            }

            blocks = currentBlock + 1;
            DicConsole.WriteLine();
            end = DateTime.UtcNow;
            mhddLog.Close();
            ibgLog.Close(dev, blocks, blockSize, (end - start).TotalSeconds, currentSpeed * 1024,
                         blockSize * (double)(blocks + 1) / 1024 / (totalDuration / 1000),
                         devicePath);
            dumpLog.WriteLine("Dump finished in {0} seconds.", (end - start).TotalSeconds);
            dumpLog.WriteLine("Average dump speed {0:F3} KiB/sec.",
                              (double)blockSize * (double)(blocks + 1) / 1024 / (totalDuration / 1000));
            dumpLog.WriteLine("Average checksum speed {0:F3} KiB/sec.",
                              (double)blockSize * (double)(blocks + 1) / 1024 / (totalChkDuration / 1000));

            DicConsole.WriteLine("Took a total of {0:F3} seconds ({1:F3} processing commands, {2:F3} checksumming).",
                                 (end - start).TotalSeconds, totalDuration / 1000, totalChkDuration / 1000);
            DicConsole.WriteLine("Avegare speed: {0:F3} MiB/sec.",
                                 (double)blockSize * (double)(blocks + 1) / 1048576 / (totalDuration / 1000));
            DicConsole.WriteLine("Fastest speed burst: {0:F3} MiB/sec.", maxSpeed);
            DicConsole.WriteLine("Slowest speed burst: {0:F3} MiB/sec.", minSpeed);

            sidecar.BlockMedia[0].Checksums  = dataChk.End().ToArray();
            sidecar.BlockMedia[0].Dimensions = Dimensions.DimensionsFromMediaType(dskType);
            CommonTypes.Metadata.MediaType.MediaTypeToString(dskType, out string xmlDskTyp, out string xmlDskSubTyp);
            sidecar.BlockMedia[0].DiskType    = xmlDskTyp;
            sidecar.BlockMedia[0].DiskSubType = xmlDskSubTyp;
            // TODO: Implement device firmware revision
            sidecar.BlockMedia[0].Image = new ImageType
            {
                format = "Raw disk image (sector by sector copy)",
                Value  = outputPrefix + ".bin"
            };
            sidecar.BlockMedia[0].LogicalBlocks        = (long)blocks;
            sidecar.BlockMedia[0].Size                 = (long)currentSize;
            sidecar.BlockMedia[0].DumpHardwareArray    = new DumpHardwareType[1];
            sidecar.BlockMedia[0].DumpHardwareArray[0] =
                new DumpHardwareType {
                Extents = new ExtentType[1]
            };
            sidecar.BlockMedia[0].DumpHardwareArray[0].Extents[0] =
                new ExtentType {
                Start = 0, End = blocks - 1
            };
            sidecar.BlockMedia[0].DumpHardwareArray[0].Manufacturer = dev.Manufacturer;
            sidecar.BlockMedia[0].DumpHardwareArray[0].Model        = dev.Model;
            sidecar.BlockMedia[0].DumpHardwareArray[0].Revision     = dev.Revision;
            sidecar.BlockMedia[0].DumpHardwareArray[0].Serial       = dev.Serial;
            sidecar.BlockMedia[0].DumpHardwareArray[0].Software     = Version.GetSoftwareType();
            sidecar.BlockMedia[0].TapeInformation = partitions.ToArray();

            if (!aborted)
            {
                DicConsole.WriteLine("Writing metadata sidecar");

                FileStream xmlFs = new FileStream(outputPrefix + ".cicm.xml", FileMode.Create);

                XmlSerializer xmlSer = new XmlSerializer(typeof(CICMMetadataType));
                xmlSer.Serialize(xmlFs, sidecar);
                xmlFs.Close();
            }

            Statistics.AddMedia(dskType, true);
        }
Exemple #5
0
        /// <summary>Creates a metadata sidecar for an optical disc (e.g. CD, DVD, GD, BD, XGD, GOD)</summary>
        /// <param name="image">Image</param>
        /// <param name="filterId">Filter uuid</param>
        /// <param name="imagePath">Image path</param>
        /// <param name="fi">Image file information</param>
        /// <param name="plugins">Image plugins</param>
        /// <param name="imgChecksums">List of image checksums</param>
        /// <param name="sidecar">Metadata sidecar</param>
        void OpticalDisc(IOpticalMediaImage image, Guid filterId, string imagePath, FileInfo fi, PluginBase plugins,
            List<ChecksumType> imgChecksums, ref CICMMetadataType sidecar, Encoding encoding)
        {
            if(aborted)
                return;

            sidecar.OpticalDisc = new[]
            {
                new OpticalDiscType
                {
                    Checksums = imgChecksums.ToArray(), Image = new ImageType
                    {
                        format = image.Format, offset = 0, offsetSpecified = true, Value = Path.GetFileName(imagePath)
                    },
                    Size = (ulong)fi.Length, Sequence = new SequenceType
                    {
                        MediaTitle = image.Info.MediaTitle
                    }
                }
            };

            if(image.Info.MediaSequence     != 0 &&
               image.Info.LastMediaSequence != 0)
            {
                sidecar.OpticalDisc[0].Sequence.MediaSequence = (uint)image.Info.MediaSequence;
                sidecar.OpticalDisc[0].Sequence.TotalMedia    = (uint)image.Info.LastMediaSequence;
            }
            else
            {
                sidecar.OpticalDisc[0].Sequence.MediaSequence = 1;
                sidecar.OpticalDisc[0].Sequence.TotalMedia    = 1;
            }

            MediaType dskType = image.Info.MediaType;

            UpdateStatus("Hashing media tags...");

            foreach(MediaTagType tagType in image.Info.ReadableMediaTags)
            {
                if(aborted)
                    return;

                switch(tagType)
                {
                    case MediaTagType.CD_ATIP:
                        sidecar.OpticalDisc[0].ATIP = new DumpType
                        {
                            Image     = Path.GetFileName(imagePath),
                            Checksums = Checksum.GetChecksums(image.ReadDiskTag(MediaTagType.CD_ATIP)).ToArray(),
                            Size      = (ulong)image.ReadDiskTag(MediaTagType.CD_ATIP).Length
                        };

                        ATIP.CDATIP? atip = ATIP.Decode(image.ReadDiskTag(MediaTagType.CD_ATIP));

                        if(atip.HasValue)
                            if(atip.Value.DDCD)
                                dskType = atip.Value.DiscType ? MediaType.DDCDRW : MediaType.DDCDR;
                            else
                                dskType = atip.Value.DiscType ? MediaType.CDRW : MediaType.CDR;

                        break;
                    case MediaTagType.DVD_BCA:
                        sidecar.OpticalDisc[0].BCA = new DumpType
                        {
                            Image     = Path.GetFileName(imagePath),
                            Checksums = Checksum.GetChecksums(image.ReadDiskTag(MediaTagType.DVD_BCA)).ToArray(),
                            Size      = (ulong)image.ReadDiskTag(MediaTagType.DVD_BCA).Length
                        };

                        break;
                    case MediaTagType.BD_BCA:
                        sidecar.OpticalDisc[0].BCA = new DumpType
                        {
                            Image     = Path.GetFileName(imagePath),
                            Checksums = Checksum.GetChecksums(image.ReadDiskTag(MediaTagType.BD_BCA)).ToArray(),
                            Size      = (ulong)image.ReadDiskTag(MediaTagType.BD_BCA).Length
                        };

                        break;
                    case MediaTagType.DVD_CMI:
                        sidecar.OpticalDisc[0].CMI = new DumpType
                        {
                            Image     = Path.GetFileName(imagePath),
                            Checksums = Checksum.GetChecksums(image.ReadDiskTag(MediaTagType.DVD_CMI)).ToArray(),
                            Size      = (ulong)image.ReadDiskTag(MediaTagType.DVD_CMI).Length
                        };

                        CSS_CPRM.LeadInCopyright? cmi =
                            CSS_CPRM.DecodeLeadInCopyright(image.ReadDiskTag(MediaTagType.DVD_CMI));

                        if(cmi.HasValue)
                            switch(cmi.Value.CopyrightType)
                            {
                                case CopyrightType.AACS:
                                    sidecar.OpticalDisc[0].CopyProtection = "AACS";

                                    break;
                                case CopyrightType.CSS:
                                    sidecar.OpticalDisc[0].CopyProtection = "CSS";

                                    break;
                                case CopyrightType.CPRM:
                                    sidecar.OpticalDisc[0].CopyProtection = "CPRM";

                                    break;
                            }

                        break;
                    case MediaTagType.DVD_DMI:
                        sidecar.OpticalDisc[0].DMI = new DumpType
                        {
                            Image     = Path.GetFileName(imagePath),
                            Checksums = Checksum.GetChecksums(image.ReadDiskTag(MediaTagType.DVD_DMI)).ToArray(),
                            Size      = (ulong)image.ReadDiskTag(MediaTagType.DVD_DMI).Length
                        };

                        if(DMI.IsXbox(image.ReadDiskTag(MediaTagType.DVD_DMI)))
                        {
                            dskType = MediaType.XGD;

                            sidecar.OpticalDisc[0].Dimensions = new DimensionsType
                            {
                                Diameter = 120, Thickness = 1.2
                            };
                        }
                        else if(DMI.IsXbox360(image.ReadDiskTag(MediaTagType.DVD_DMI)))
                        {
                            dskType = MediaType.XGD2;

                            sidecar.OpticalDisc[0].Dimensions = new DimensionsType
                            {
                                Diameter = 120, Thickness = 1.2
                            };
                        }

                        break;
                    case MediaTagType.DVD_PFI:
                        sidecar.OpticalDisc[0].PFI = new DumpType
                        {
                            Image     = Path.GetFileName(imagePath),
                            Checksums = Checksum.GetChecksums(image.ReadDiskTag(MediaTagType.DVD_PFI)).ToArray(),
                            Size      = (ulong)image.ReadDiskTag(MediaTagType.DVD_PFI).Length
                        };

                        PFI.PhysicalFormatInformation? pfi = PFI.Decode(image.ReadDiskTag(MediaTagType.DVD_PFI));

                        if(pfi.HasValue)
                            if(dskType != MediaType.XGD    &&
                               dskType != MediaType.XGD2   &&
                               dskType != MediaType.XGD3   &&
                               dskType != MediaType.PS2DVD &&
                               dskType != MediaType.PS3DVD &&
                               dskType != MediaType.Nuon)
                            {
                                switch(pfi.Value.DiskCategory)
                                {
                                    case DiskCategory.DVDPR:
                                        dskType = MediaType.DVDPR;

                                        break;
                                    case DiskCategory.DVDPRDL:
                                        dskType = MediaType.DVDPRDL;

                                        break;
                                    case DiskCategory.DVDPRW:
                                        dskType = MediaType.DVDPRW;

                                        break;
                                    case DiskCategory.DVDPRWDL:
                                        dskType = MediaType.DVDPRWDL;

                                        break;
                                    case DiskCategory.DVDR:
                                        dskType = MediaType.DVDR;

                                        break;
                                    case DiskCategory.DVDRAM:
                                        dskType = MediaType.DVDRAM;

                                        break;
                                    case DiskCategory.DVDROM:
                                        dskType = MediaType.DVDROM;

                                        break;
                                    case DiskCategory.DVDRW:
                                        dskType = MediaType.DVDRW;

                                        break;
                                    case DiskCategory.HDDVDR:
                                        dskType = MediaType.HDDVDR;

                                        break;
                                    case DiskCategory.HDDVDRAM:
                                        dskType = MediaType.HDDVDRAM;

                                        break;
                                    case DiskCategory.HDDVDROM:
                                        dskType = MediaType.HDDVDROM;

                                        break;
                                    case DiskCategory.HDDVDRW:
                                        dskType = MediaType.HDDVDRW;

                                        break;
                                    case DiskCategory.Nintendo:
                                        dskType = MediaType.GOD;

                                        break;
                                    case DiskCategory.UMD:
                                        dskType = MediaType.UMD;

                                        break;
                                }

                                if(dskType               == MediaType.DVDR &&
                                   pfi.Value.PartVersion == 6)
                                    dskType = MediaType.DVDRDL;

                                if(dskType               == MediaType.DVDRW &&
                                   pfi.Value.PartVersion == 3)
                                    dskType = MediaType.DVDRWDL;

                                if(dskType            == MediaType.GOD &&
                                   pfi.Value.DiscSize == DVDSize.OneTwenty)
                                    dskType = MediaType.WOD;

                                sidecar.OpticalDisc[0].Dimensions = new DimensionsType();

                                if(dskType == MediaType.UMD)
                                {
                                    sidecar.OpticalDisc[0].Dimensions.Height          = 64;
                                    sidecar.OpticalDisc[0].Dimensions.HeightSpecified = true;
                                    sidecar.OpticalDisc[0].Dimensions.Width           = 63;
                                    sidecar.OpticalDisc[0].Dimensions.WidthSpecified  = true;
                                    sidecar.OpticalDisc[0].Dimensions.Thickness       = 4;
                                }
                                else
                                    switch(pfi.Value.DiscSize)
                                    {
                                        case DVDSize.Eighty:
                                            sidecar.OpticalDisc[0].Dimensions.Diameter          = 80;
                                            sidecar.OpticalDisc[0].Dimensions.DiameterSpecified = true;
                                            sidecar.OpticalDisc[0].Dimensions.Thickness         = 1.2;

                                            break;
                                        case DVDSize.OneTwenty:
                                            sidecar.OpticalDisc[0].Dimensions.Diameter          = 120;
                                            sidecar.OpticalDisc[0].Dimensions.DiameterSpecified = true;
                                            sidecar.OpticalDisc[0].Dimensions.Thickness         = 1.2;

                                            break;
                                    }
                            }

                        break;
                    case MediaTagType.CD_PMA:
                        sidecar.OpticalDisc[0].PMA = new DumpType
                        {
                            Image     = Path.GetFileName(imagePath),
                            Checksums = Checksum.GetChecksums(image.ReadDiskTag(MediaTagType.CD_PMA)).ToArray(),
                            Size      = (ulong)image.ReadDiskTag(MediaTagType.CD_PMA).Length
                        };

                        break;
                    case MediaTagType.CD_FullTOC:
                        sidecar.OpticalDisc[0].TOC = new DumpType
                        {
                            Image     = Path.GetFileName(imagePath),
                            Checksums = Checksum.GetChecksums(image.ReadDiskTag(MediaTagType.CD_FullTOC)).ToArray(),
                            Size      = (ulong)image.ReadDiskTag(MediaTagType.CD_FullTOC).Length
                        };

                        break;
                    case MediaTagType.CD_FirstTrackPregap:
                        sidecar.OpticalDisc[0].FirstTrackPregrap = new[]
                        {
                            new BorderType
                            {
                                Image = Path.GetFileName(imagePath),
                                Checksums = Checksum.GetChecksums(image.ReadDiskTag(MediaTagType.CD_FirstTrackPregap)).
                                                     ToArray(),
                                Size = (ulong)image.ReadDiskTag(MediaTagType.CD_FirstTrackPregap).Length
                            }
                        };

                        break;
                    case MediaTagType.CD_LeadIn:
                        sidecar.OpticalDisc[0].LeadIn = new[]
                        {
                            new BorderType
                            {
                                Image     = Path.GetFileName(imagePath),
                                Checksums = Checksum.GetChecksums(image.ReadDiskTag(MediaTagType.CD_LeadIn)).ToArray(),
                                Size      = (ulong)image.ReadDiskTag(MediaTagType.CD_LeadIn).Length
                            }
                        };

                        break;
                    case MediaTagType.Xbox_SecuritySector:
                        if(sidecar.OpticalDisc[0].Xbox == null)
                            sidecar.OpticalDisc[0].Xbox = new XboxType();

                        sidecar.OpticalDisc[0].Xbox.SecuritySectors = new[]
                        {
                            new XboxSecuritySectorsType
                            {
                                RequestNumber = 0, RequestVersion = 1, SecuritySectors = new DumpType
                                {
                                    Image = Path.GetFileName(imagePath),
                                    Checksums = Checksum.
                                                GetChecksums(image.ReadDiskTag(MediaTagType.Xbox_SecuritySector)).
                                                ToArray(),
                                    Size = (ulong)image.ReadDiskTag(MediaTagType.Xbox_SecuritySector).Length
                                }
                            }
                        };

                        break;
                    case MediaTagType.Xbox_PFI:
                        if(sidecar.OpticalDisc[0].Xbox == null)
                            sidecar.OpticalDisc[0].Xbox = new XboxType();

                        sidecar.OpticalDisc[0].Xbox.PFI = new DumpType
                        {
                            Image     = Path.GetFileName(imagePath),
                            Checksums = Checksum.GetChecksums(image.ReadDiskTag(MediaTagType.Xbox_PFI)).ToArray(),
                            Size      = (ulong)image.ReadDiskTag(MediaTagType.Xbox_PFI).Length
                        };

                        break;
                    case MediaTagType.Xbox_DMI:
                        if(sidecar.OpticalDisc[0].Xbox == null)
                            sidecar.OpticalDisc[0].Xbox = new XboxType();

                        sidecar.OpticalDisc[0].Xbox.DMI = new DumpType
                        {
                            Image     = Path.GetFileName(imagePath),
                            Checksums = Checksum.GetChecksums(image.ReadDiskTag(MediaTagType.Xbox_DMI)).ToArray(),
                            Size      = (ulong)image.ReadDiskTag(MediaTagType.Xbox_DMI).Length
                        };

                        break;
                }
            }

            try
            {
                List<Session> sessions = image.Sessions;
                sidecar.OpticalDisc[0].Sessions = (uint)(sessions?.Count ?? 1);
            }
            catch
            {
                sidecar.OpticalDisc[0].Sessions = 1;
            }

            List<Track>     tracks  = image.Tracks;
            List<TrackType> trksLst = null;

            if(tracks != null)
            {
                sidecar.OpticalDisc[0].Tracks    = new uint[1];
                sidecar.OpticalDisc[0].Tracks[0] = (uint)tracks.Count;
                trksLst                          = new List<TrackType>();
            }

            if(sidecar.OpticalDisc[0].Dimensions == null &&
               image.Info.MediaType              != MediaType.Unknown)
                sidecar.OpticalDisc[0].Dimensions = Dimensions.DimensionsFromMediaType(image.Info.MediaType);

            if(aborted)
                return;

            InitProgress();

            UpdateStatus("Checking filesystems");
            List<Partition> partitions = Partitions.GetAll(image);
            Partitions.AddSchemesToStats(partitions);

            UpdateStatus("Hashing tracks...");

            foreach(Track trk in tracks)
            {
                if(aborted)
                {
                    EndProgress();

                    return;
                }

                var xmlTrk = new TrackType();

                switch(trk.TrackType)
                {
                    case CommonTypes.Enums.TrackType.Audio:
                        xmlTrk.TrackType1 = TrackTypeTrackType.audio;

                        break;
                    case CommonTypes.Enums.TrackType.CdMode2Form2:
                        xmlTrk.TrackType1 = TrackTypeTrackType.m2f2;

                        break;
                    case CommonTypes.Enums.TrackType.CdMode2Formless:
                        xmlTrk.TrackType1 = TrackTypeTrackType.mode2;

                        break;
                    case CommonTypes.Enums.TrackType.CdMode2Form1:
                        xmlTrk.TrackType1 = TrackTypeTrackType.m2f1;

                        break;
                    case CommonTypes.Enums.TrackType.CdMode1:
                        xmlTrk.TrackType1 = TrackTypeTrackType.mode1;

                        break;
                    case CommonTypes.Enums.TrackType.Data:
                        switch(sidecar.OpticalDisc[0].DiscType)
                        {
                            case"BD":
                                xmlTrk.TrackType1 = TrackTypeTrackType.bluray;

                                break;
                            case"DDCD":
                                xmlTrk.TrackType1 = TrackTypeTrackType.ddcd;

                                break;
                            case"DVD":
                                xmlTrk.TrackType1 = TrackTypeTrackType.dvd;

                                break;
                            case"HD DVD":
                                xmlTrk.TrackType1 = TrackTypeTrackType.hddvd;

                                break;
                            default:
                                xmlTrk.TrackType1 = TrackTypeTrackType.mode1;

                                break;
                        }

                        break;
                }

                xmlTrk.Sequence = new TrackSequenceType
                {
                    Session = trk.TrackSession, TrackNumber = trk.TrackSequence
                };

                xmlTrk.StartSector = trk.TrackStartSector;
                xmlTrk.EndSector   = trk.TrackEndSector;

                if(trk.Indexes != null &&
                   trk.Indexes.ContainsKey(0))
                    if(trk.Indexes.TryGetValue(0, out ulong idx0))
                        xmlTrk.StartSector = idx0;

                switch(sidecar.OpticalDisc[0].DiscType)
                {
                    case"CD":
                    case"GD":
                        xmlTrk.StartMSF = LbaToMsf((long)xmlTrk.StartSector);
                        xmlTrk.EndMSF   = LbaToMsf((long)xmlTrk.EndSector);

                        break;
                    case"DDCD":
                        xmlTrk.StartMSF = DdcdLbaToMsf((long)xmlTrk.StartSector);
                        xmlTrk.EndMSF   = DdcdLbaToMsf((long)xmlTrk.EndSector);

                        break;
                }

                xmlTrk.Image = new ImageType
                {
                    Value = Path.GetFileName(trk.TrackFile), format = trk.TrackFileType
                };

                if(trk.TrackFileOffset > 0)
                {
                    xmlTrk.Image.offset          = trk.TrackFileOffset;
                    xmlTrk.Image.offsetSpecified = true;
                }

                xmlTrk.Size           = (xmlTrk.EndSector - xmlTrk.StartSector + 1) * (ulong)trk.TrackRawBytesPerSector;
                xmlTrk.BytesPerSector = (uint)trk.TrackBytesPerSector;

                uint  sectorsToRead = 512;
                ulong sectors       = xmlTrk.EndSector - xmlTrk.StartSector + 1;
                ulong doneSectors   = 0;

                // If there is only one track, and it's the same as the image file (e.g. ".iso" files), don't re-checksum.
                if(image.Id == new Guid("12345678-AAAA-BBBB-CCCC-123456789000") &&

                   // Only if filter is none...
                   (filterId == new Guid("12345678-AAAA-BBBB-CCCC-123456789000") ||

                    // ...or AppleDouble
                    filterId == new Guid("1b2165ee-c9df-4b21-bbbb-9e5892b2df4d")))
                    xmlTrk.Checksums = sidecar.OpticalDisc[0].Checksums;
                else
                {
                    UpdateProgress("Track {0} of {1}", trk.TrackSequence, tracks.Count);

                    // For fast debugging, skip checksum
                    //goto skipChecksum;

                    var trkChkWorker = new Checksum();

                    InitProgress2();

                    while(doneSectors < sectors)
                    {
                        if(aborted)
                        {
                            EndProgress();
                            EndProgress2();

                            return;
                        }

                        byte[] sector;

                        if(sectors - doneSectors >= sectorsToRead)
                        {
                            sector = image.ReadSectorsLong(doneSectors, sectorsToRead, xmlTrk.Sequence.TrackNumber);

                            UpdateProgress2("Hashings sector {0} of {1}", (long)doneSectors,
                                            (long)(trk.TrackEndSector - trk.TrackStartSector + 1));

                            doneSectors += sectorsToRead;
                        }
                        else
                        {
                            sector = image.ReadSectorsLong(doneSectors, (uint)(sectors - doneSectors),
                                                           xmlTrk.Sequence.TrackNumber);

                            UpdateProgress2("Hashings sector {0} of {1}", (long)doneSectors,
                                            (long)(trk.TrackEndSector - trk.TrackStartSector + 1));

                            doneSectors += sectors - doneSectors;
                        }

                        trkChkWorker.Update(sector);
                    }

                    List<ChecksumType> trkChecksums = trkChkWorker.End();

                    xmlTrk.Checksums = trkChecksums.ToArray();

                    EndProgress2();
                }

                if(trk.TrackSubchannelType != TrackSubchannelType.None)
                {
                    xmlTrk.SubChannel = new SubChannelType
                    {
                        Image = new ImageType
                        {
                            Value = trk.TrackSubchannelFile
                        },

                        // TODO: Packed subchannel has different size?
                        Size = (xmlTrk.EndSector - xmlTrk.StartSector + 1) * 96
                    };

                    switch(trk.TrackSubchannelType)
                    {
                        case TrackSubchannelType.Packed:
                        case TrackSubchannelType.PackedInterleaved:
                            xmlTrk.SubChannel.Image.format = "rw";

                            break;
                        case TrackSubchannelType.Raw:
                        case TrackSubchannelType.RawInterleaved:
                            xmlTrk.SubChannel.Image.format = "rw_raw";

                            break;
                        case TrackSubchannelType.Q16:
                        case TrackSubchannelType.Q16Interleaved:
                            xmlTrk.SubChannel.Image.format = "q16";

                            break;
                    }

                    if(trk.TrackFileOffset > 0)
                    {
                        xmlTrk.SubChannel.Image.offset          = trk.TrackSubchannelOffset;
                        xmlTrk.SubChannel.Image.offsetSpecified = true;
                    }

                    var subChkWorker = new Checksum();

                    sectors     = xmlTrk.EndSector - xmlTrk.StartSector + 1;
                    doneSectors = 0;

                    InitProgress2();

                    while(doneSectors < sectors)
                    {
                        if(aborted)
                        {
                            EndProgress();
                            EndProgress2();

                            return;
                        }

                        byte[] sector;

                        if(sectors - doneSectors >= sectorsToRead)
                        {
                            sector = image.ReadSectorsTag(doneSectors, sectorsToRead, xmlTrk.Sequence.TrackNumber,
                                                          SectorTagType.CdSectorSubchannel);

                            UpdateProgress2("Hashings subchannel sector {0} of {1}", (long)doneSectors,
                                            (long)(trk.TrackEndSector - trk.TrackStartSector + 1));

                            doneSectors += sectorsToRead;
                        }
                        else
                        {
                            sector = image.ReadSectorsTag(doneSectors, (uint)(sectors - doneSectors),
                                                          xmlTrk.Sequence.TrackNumber,
                                                          SectorTagType.CdSectorSubchannel);

                            UpdateProgress2("Hashings subchannel sector {0} of {1}", (long)doneSectors,
                                            (long)(trk.TrackEndSector - trk.TrackStartSector + 1));

                            doneSectors += sectors - doneSectors;
                        }

                        subChkWorker.Update(sector);
                    }

                    List<ChecksumType> subChecksums = subChkWorker.End();

                    xmlTrk.SubChannel.Checksums = subChecksums.ToArray();

                    EndProgress2();
                }

                // For fast debugging, skip checksum
                //skipChecksum:

                List<Partition> trkPartitions = partitions.
                                                Where(p => p.Start >= trk.TrackStartSector &&
                                                           p.End   <= trk.TrackEndSector).ToList();

                xmlTrk.FileSystemInformation = new PartitionType[1];

                if(trkPartitions.Count > 0)
                {
                    xmlTrk.FileSystemInformation = new PartitionType[trkPartitions.Count];

                    for(int i = 0; i < trkPartitions.Count; i++)
                    {
                        xmlTrk.FileSystemInformation[i] = new PartitionType
                        {
                            Description = trkPartitions[i].Description, EndSector = trkPartitions[i].End,
                            Name        = trkPartitions[i].Name, Sequence         = (uint)trkPartitions[i].Sequence,
                            StartSector = trkPartitions[i].Start, Type            = trkPartitions[i].Type
                        };

                        List<FileSystemType> lstFs = new List<FileSystemType>();

                        foreach(IFilesystem plugin in plugins.PluginsList.Values)
                            try
                            {
                                if(aborted)
                                {
                                    EndProgress();

                                    return;
                                }

                                if(!plugin.Identify(image, trkPartitions[i]))
                                    continue;

                                plugin.GetInformation(image, trkPartitions[i], out _, encoding);
                                lstFs.Add(plugin.XmlFsType);
                                Statistics.AddFilesystem(plugin.XmlFsType.Type);

                                switch(plugin.XmlFsType.Type)
                                {
                                    case"Opera":
                                        dskType = MediaType.ThreeDO;

                                        break;
                                    case"PC Engine filesystem":
                                        dskType = MediaType.SuperCDROM2;

                                        break;
                                    case"Nintendo Wii filesystem":
                                        dskType = MediaType.WOD;

                                        break;
                                    case"Nintendo Gamecube filesystem":
                                        dskType = MediaType.GOD;

                                        break;
                                }
                            }
                            #pragma warning disable RECS0022 // A catch clause that catches System.Exception and has an empty body
                            catch
                                #pragma warning restore RECS0022 // A catch clause that catches System.Exception and has an empty body
                            {
                                //DicConsole.DebugWriteLine("Create-sidecar command", "Plugin {0} crashed", _plugin.Name);
                            }

                        if(lstFs.Count > 0)
                            xmlTrk.FileSystemInformation[i].FileSystems = lstFs.ToArray();
                    }
                }
                else
                {
                    xmlTrk.FileSystemInformation[0] = new PartitionType
                    {
                        EndSector = xmlTrk.EndSector, StartSector = xmlTrk.StartSector
                    };

                    List<FileSystemType> lstFs = new List<FileSystemType>();

                    var xmlPart = new Partition
                    {
                        Start = xmlTrk.StartSector, Length         = xmlTrk.EndSector - xmlTrk.StartSector + 1,
                        Type  = xmlTrk.TrackType1.ToString(), Size = xmlTrk.Size, Sequence = xmlTrk.Sequence.TrackNumber
                    };

                    foreach(IFilesystem plugin in plugins.PluginsList.Values)
                        try
                        {
                            if(aborted)
                            {
                                EndProgress();

                                return;
                            }

                            if(!plugin.Identify(image, xmlPart))
                                continue;

                            plugin.GetInformation(image, xmlPart, out _, encoding);
                            lstFs.Add(plugin.XmlFsType);
                            Statistics.AddFilesystem(plugin.XmlFsType.Type);

                            switch(plugin.XmlFsType.Type)
                            {
                                case"Opera":
                                    dskType = MediaType.ThreeDO;

                                    break;
                                case"PC Engine filesystem":
                                    dskType = MediaType.SuperCDROM2;

                                    break;
                                case"Nintendo Wii filesystem":
                                    dskType = MediaType.WOD;

                                    break;
                                case"Nintendo Gamecube filesystem":
                                    dskType = MediaType.GOD;

                                    break;
                            }
                        }
                        #pragma warning disable RECS0022 // A catch clause that catches System.Exception and has an empty body
                        catch
                            #pragma warning restore RECS0022 // A catch clause that catches System.Exception and has an empty body
                        {
                            //DicConsole.DebugWriteLine("Create-sidecar command", "Plugin {0} crashed", _plugin.Name);
                        }

                    if(lstFs.Count > 0)
                        xmlTrk.FileSystemInformation[0].FileSystems = lstFs.ToArray();
                }

                trksLst.Add(xmlTrk);
            }

            EndProgress();

            if(trksLst != null)
                sidecar.OpticalDisc[0].Track = trksLst.ToArray();

            // All XGD3 all have the same number of blocks
            if(dskType                             == MediaType.XGD2 &&
               sidecar.OpticalDisc[0].Track.Length == 1)
            {
                ulong blocks = sidecar.OpticalDisc[0].Track[0].EndSector - sidecar.OpticalDisc[0].Track[0].StartSector +
                               1;

                if(blocks == 25063   || // Locked (or non compatible drive)
                   blocks == 4229664 || // Xtreme unlock
                   blocks == 4246304)   // Wxripper unlock
                    dskType = MediaType.XGD3;
            }

            (string type, string subType) discType = CommonTypes.Metadata.MediaType.MediaTypeToString(dskType);
            sidecar.OpticalDisc[0].DiscType    = discType.type;
            sidecar.OpticalDisc[0].DiscSubType = discType.subType;
            Statistics.AddMedia(dskType, false);

            if(image.DumpHardware != null)
                sidecar.OpticalDisc[0].DumpHardwareArray = image.DumpHardware.ToArray();
            else if(!string.IsNullOrEmpty(image.Info.DriveManufacturer)     ||
                    !string.IsNullOrEmpty(image.Info.DriveModel)            ||
                    !string.IsNullOrEmpty(image.Info.DriveFirmwareRevision) ||
                    !string.IsNullOrEmpty(image.Info.DriveSerialNumber))
                sidecar.OpticalDisc[0].DumpHardwareArray = new[]
                {
                    new DumpHardwareType
                    {
                        Extents = new[]
                        {
                            new ExtentType
                            {
                                Start = 0, End = image.Info.Sectors
                            }
                        },
                        Manufacturer = image.Info.DriveManufacturer, Model      = image.Info.DriveModel,
                        Firmware     = image.Info.DriveFirmwareRevision, Serial = image.Info.DriveSerialNumber,
                        Software =
                            new SoftwareType
                            {
                                Name = image.Info.Application, Version = image.Info.ApplicationVersion
                            }
                    }
                };
        }