/// <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; } } }