From 9d4f8086aca0da81ea15ede09afe0226dad01d46 Mon Sep 17 00:00:00 2001 From: Natalia Portillo Date: Thu, 18 Jan 2018 15:17:44 +0000 Subject: [PATCH] Add support for media tags on raw sector-by-sector images. --- DiscImageChef.DiscImages/Enums.cs | 18 +- DiscImageChef.DiscImages/ZZZRawImage.cs | 400 ++++++++++++++++++++++-- 2 files changed, 386 insertions(+), 32 deletions(-) diff --git a/DiscImageChef.DiscImages/Enums.cs b/DiscImageChef.DiscImages/Enums.cs index 87d0f1ae..3eaa33db 100644 --- a/DiscImageChef.DiscImages/Enums.cs +++ b/DiscImageChef.DiscImages/Enums.cs @@ -250,7 +250,23 @@ namespace DiscImageChef.DiscImages /// On floppy disks, data in last cylinder usually in a different format that contains duplication or /// manufacturing information /// - Floppy_LeadOut + Floppy_LeadOut, + /// DVD Disc Control Blocks + DCB, + /// Compact Disc Lead-in + CD_LeadIn, + /// Compact Disc Lead-out + CD_LeadOut, + /// SCSI MODE SENSE (6) + SCSI_MODESENSE_6, + /// SCSI MODE SENSE (10) + SCSI_MODESENSE_10, + /// USB descriptors + USB_Descriptors, + /// XGD unlocked DMI + Xbox_DMI, + /// XDG unlocked PFI + Xbox_PFI, } /// diff --git a/DiscImageChef.DiscImages/ZZZRawImage.cs b/DiscImageChef.DiscImages/ZZZRawImage.cs index 7d6d78f1..b5f95dfd 100644 --- a/DiscImageChef.DiscImages/ZZZRawImage.cs +++ b/DiscImageChef.DiscImages/ZZZRawImage.cs @@ -36,17 +36,52 @@ using System.IO; using System.Linq; using DiscImageChef.CommonTypes; using DiscImageChef.Console; +using DiscImageChef.Decoders.ATA; +using DiscImageChef.Decoders.CD; +using DiscImageChef.Decoders.DVD; +using DiscImageChef.Decoders.SCSI; using DiscImageChef.Filters; +using DMI = DiscImageChef.Decoders.Xbox.DMI; namespace DiscImageChef.DiscImages { public class ZZZRawImage : IWritableImage { - bool differentTrackZeroSize; - string extension; - ImageInfo imageInfo; - IFilter rawImageFilter; - FileStream writingStream; + readonly (MediaTagType tag, string name)[] readWriteSidecars = + { + (MediaTagType.ATA_IDENTIFY, ".identify.bin"), (MediaTagType.BD_DI, ".di.bin"), + (MediaTagType.CD_ATIP, ".atip.bin"), (MediaTagType.CD_FullTOC, ".toc.bin"), + (MediaTagType.CD_LeadIn, ".leadin.bin"), (MediaTagType.CD_PMA, ".pma.bin"), + (MediaTagType.CD_TEXT, ".cdtext.bin"), (MediaTagType.DCB, ".dcb.bin"), (MediaTagType.DVD_ADIP, ".adip.bin"), + (MediaTagType.DVD_BCA, ".bca.bin"), (MediaTagType.DVD_CMI, ".cmi.bin"), (MediaTagType.DVD_DMI, ".dmi.bin"), + (MediaTagType.DVD_MediaIdentifier, ".mid.bin"), (MediaTagType.DVD_PFI, ".pfi.bin"), + (MediaTagType.DVDRAM_DDS, ".dds.bin"), (MediaTagType.DVDRAM_SpareArea, ".sai.bin"), + (MediaTagType.DVDR_PFI, ".pfir.bin"), (MediaTagType.DVDR_PreRecordedInfo, ".pri.bin"), + (MediaTagType.Floppy_LeadOut, ".leadout.bin"), (MediaTagType.HDDVD_CPI, ".cpi.bin"), + (MediaTagType.MMC_ExtendedCSD, ".ecsd.bin"), (MediaTagType.PCMCIA_CIS, ".cis.bin"), + (MediaTagType.SCSI_INQUIRY, ".inquiry.bin"), (MediaTagType.SCSI_MODEPAGE_2A, ".modepage2a.bin"), + (MediaTagType.SCSI_MODESENSE_10, ".modesense10.bin"), (MediaTagType.SCSI_MODESENSE_6, ".modesense.bin"), + (MediaTagType.SD_CID, ".cid.bin"), (MediaTagType.SD_CSD, ".csd.bin"), (MediaTagType.SD_OCR, ".ocr.bin"), + (MediaTagType.SD_SCR, ".scr.bin"), (MediaTagType.USB_Descriptors, ".usbdescriptors.bin"), + (MediaTagType.Xbox_DMI, ".xboxdmi.bin"), (MediaTagType.Xbox_PFI, ".xboxpfi.bin"), + (MediaTagType.Xbox_SecuritySector, ".ss.bin") + }; + + readonly (MediaTagType tag, string name)[] writeOnlySidecars = + { + (MediaTagType.ATAPI_IDENTIFY, ".identify.bin"), (MediaTagType.BD_BCA, ".bca.bin"), + (MediaTagType.BD_DDS, ".dds.bin"), (MediaTagType.BD_DI, ".di.bin"), (MediaTagType.BD_SpareArea, ".sai.bin"), + (MediaTagType.CD_LeadOut, ".leadout.bin"), (MediaTagType.MMC_CID, ".cid.bin"), + (MediaTagType.MMC_CSD, ".csd.bin"), (MediaTagType.MMC_OCR, ".ocr.bin") + }; + + string basepath; + bool differentTrackZeroSize; + string extension; + ImageInfo imageInfo; + Dictionary mediaTags; + IFilter rawImageFilter; + FileStream writingStream; public ZZZRawImage() { @@ -372,22 +407,6 @@ namespace DiscImageChef.DiscImages imageInfo.MediaType = CalculateDiskType(); - switch(imageInfo.MediaType) - { - case MediaType.CD: - case MediaType.DVDPR: - case MediaType.DVDR: - case MediaType.DVDRDL: - case MediaType.DVDPRDL: - case MediaType.BDR: - case MediaType.BDRXL: - imageInfo.XmlMediaType = XmlMediaType.OpticalDisc; - break; - default: - imageInfo.XmlMediaType = XmlMediaType.BlockMedia; - break; - } - // Sharp X68000 SASI hard disks if(extension == ".hdf") if(imageInfo.ImageSize % 256 == 0) @@ -397,13 +416,180 @@ namespace DiscImageChef.DiscImages imageInfo.MediaType = MediaType.GENERIC_HDD; } - if(imageInfo.XmlMediaType == XmlMediaType.OpticalDisc) + // Search for known tags + string basename = imageFilter.GetBasePath(); + basename = basename.Substring(0, basename.Length - extension.Length); + + mediaTags = new Dictionary(); + foreach((MediaTagType tag, string name) sidecar in readWriteSidecars) { - imageInfo.HasSessions = true; - imageInfo.HasPartitions = true; + FiltersList filters = new FiltersList(); + IFilter filter = filters.GetFilter(basename + sidecar.name); + if(filter == null || !filter.IsOpened()) continue; + + DicConsole.DebugWriteLine("ZZZRawImage Plugin", "Found media tag {0}", sidecar.tag); + byte[] data = new byte[filter.GetDataForkLength()]; + filter.GetDataForkStream().Read(data, 0, data.Length); + mediaTags.Add(sidecar.tag, data); } - DicConsole.VerboseWriteLine("Raw disk image contains a disk of type {0}", imageInfo.MediaType); + // If there are INQUIRY and IDENTIFY tags, it's ATAPI + if(mediaTags.ContainsKey(MediaTagType.SCSI_INQUIRY)) + if(mediaTags.TryGetValue(MediaTagType.ATA_IDENTIFY, out byte[] tag)) + { + mediaTags.Remove(MediaTagType.ATA_IDENTIFY); + mediaTags.Add(MediaTagType.ATAPI_IDENTIFY, tag); + } + + // It is a blu-ray + if(mediaTags.ContainsKey(MediaTagType.BD_DI)) + { + imageInfo.MediaType = MediaType.BDROM; + + if(mediaTags.TryGetValue(MediaTagType.DVD_BCA, out byte[] bca)) + { + mediaTags.Remove(MediaTagType.DVD_BCA); + mediaTags.Add(MediaTagType.BD_BCA, bca); + } + + if(mediaTags.TryGetValue(MediaTagType.DVDRAM_DDS, out byte[] dds)) + { + imageInfo.MediaType = MediaType.BDRE; + mediaTags.Remove(MediaTagType.DVDRAM_DDS); + mediaTags.Add(MediaTagType.BD_DDS, dds); + } + + if(mediaTags.TryGetValue(MediaTagType.DVDRAM_SpareArea, out byte[] sai)) + { + imageInfo.MediaType = MediaType.BDRE; + mediaTags.Remove(MediaTagType.DVDRAM_SpareArea); + mediaTags.Add(MediaTagType.BD_SpareArea, sai); + } + } + + // It is a DVD + if(mediaTags.TryGetValue(MediaTagType.DVD_PFI, out byte[] pfi)) + { + PFI.PhysicalFormatInformation decPfi = PFI.Decode(pfi).Value; + switch(decPfi.DiskCategory) + { + case DiskCategory.DVDPR: + imageInfo.MediaType = MediaType.DVDPR; + break; + case DiskCategory.DVDPRDL: + imageInfo.MediaType = MediaType.DVDPRDL; + break; + case DiskCategory.DVDPRW: + imageInfo.MediaType = MediaType.DVDPRW; + break; + case DiskCategory.DVDPRWDL: + imageInfo.MediaType = MediaType.DVDPRWDL; + break; + case DiskCategory.DVDR: + imageInfo.MediaType = decPfi.PartVersion == 6 ? MediaType.DVDRDL : MediaType.DVDR; + break; + case DiskCategory.DVDRAM: + imageInfo.MediaType = MediaType.DVDRAM; + break; + default: + imageInfo.MediaType = MediaType.DVDROM; + break; + case DiskCategory.DVDRW: + imageInfo.MediaType = decPfi.PartVersion == 3 ? MediaType.DVDRWDL : MediaType.DVDRW; + break; + case DiskCategory.HDDVDR: + imageInfo.MediaType = MediaType.HDDVDR; + break; + case DiskCategory.HDDVDRAM: + imageInfo.MediaType = MediaType.HDDVDRAM; + break; + case DiskCategory.HDDVDROM: + imageInfo.MediaType = MediaType.HDDVDROM; + break; + case DiskCategory.HDDVDRW: + imageInfo.MediaType = MediaType.HDDVDRW; + break; + case DiskCategory.Nintendo: + imageInfo.MediaType = decPfi.DiscSize == DVDSize.Eighty ? MediaType.GOD : MediaType.WOD; + break; + case DiskCategory.UMD: + imageInfo.MediaType = MediaType.UMD; + break; + } + + if((imageInfo.MediaType == MediaType.DVDR || imageInfo.MediaType == MediaType.DVDRW || + imageInfo.MediaType == MediaType.HDDVDR) && + mediaTags.TryGetValue(MediaTagType.DVD_MediaIdentifier, out byte[] mid)) + { + mediaTags.Remove(MediaTagType.DVD_MediaIdentifier); + mediaTags.Add(MediaTagType.DVDR_MediaIdentifier, mid); + } + + // Check for Xbox + if(mediaTags.TryGetValue(MediaTagType.DVD_DMI, out byte[] dmi)) + if(DMI.IsXbox(dmi) || DMI.IsXbox360(dmi)) + if(DMI.IsXbox(dmi)) + imageInfo.MediaType = MediaType.XGD; + else if(DMI.IsXbox360(dmi)) + { + imageInfo.MediaType = MediaType.XGD2; + + // All XGD3 all have the same number of blocks + if(imageInfo.Sectors == 25063 || // Locked (or non compatible drive) + imageInfo.Sectors == 4229664 || // Xtreme unlock + imageInfo.Sectors == 4246304) // Wxripper unlock + imageInfo.MediaType = MediaType.XGD3; + } + } + + // It's MultiMediaCard or SecureDigital + if(mediaTags.ContainsKey(MediaTagType.SD_CID) || mediaTags.ContainsKey(MediaTagType.SD_CSD) || + mediaTags.ContainsKey(MediaTagType.SD_OCR)) + { + imageInfo.MediaType = MediaType.SecureDigital; + + if(mediaTags.ContainsKey(MediaTagType.MMC_ExtendedCSD) || !mediaTags.ContainsKey(MediaTagType.SD_SCR)) + { + imageInfo.MediaType = MediaType.MMC; + + if(mediaTags.TryGetValue(MediaTagType.SD_CID, out byte[] cid)) + { + mediaTags.Remove(MediaTagType.SD_CID); + mediaTags.Add(MediaTagType.MMC_CID, cid); + } + + if(mediaTags.TryGetValue(MediaTagType.SD_CSD, out byte[] csd)) + { + mediaTags.Remove(MediaTagType.SD_CSD); + mediaTags.Add(MediaTagType.MMC_CSD, csd); + } + + if(mediaTags.TryGetValue(MediaTagType.SD_OCR, out byte[] ocr)) + { + mediaTags.Remove(MediaTagType.SD_OCR); + mediaTags.Add(MediaTagType.MMC_OCR, ocr); + } + } + } + + // It's a compact disc + if(mediaTags.ContainsKey(MediaTagType.CD_FullTOC)) + { + imageInfo.MediaType = imageInfo.Sectors > 360000 ? MediaType.DDCD : MediaType.CD; + + // Only CD-R and CD-RW have ATIP + if(mediaTags.TryGetValue(MediaTagType.CD_ATIP, out byte[] atipBuf)) + { + ATIP.CDATIP? atip = ATIP.Decode(atipBuf); + if(atip.HasValue) imageInfo.MediaType = atip.Value.DiscType ? MediaType.CDRW : MediaType.CDR; + } + + if(mediaTags.TryGetValue(MediaTagType.Floppy_LeadOut, out byte[] leadout)) + { + mediaTags.Remove(MediaTagType.Floppy_LeadOut); + mediaTags.Add(MediaTagType.CD_LeadOut, leadout); + } + } switch(imageInfo.MediaType) { @@ -725,6 +911,134 @@ namespace DiscImageChef.DiscImages break; } + // It's SCSI, check tags + if(mediaTags.ContainsKey(MediaTagType.SCSI_INQUIRY)) + { + PeripheralDeviceTypes devType = PeripheralDeviceTypes.DirectAccess; + Inquiry.SCSIInquiry? scsiInq; + if(mediaTags.TryGetValue(MediaTagType.SCSI_INQUIRY, out byte[] inq)) + { + scsiInq = Inquiry.Decode(inq); + devType = (PeripheralDeviceTypes)(inq[0] & 0x1F); + } + + Modes.DecodedMode? decMode = null; + + if(mediaTags.TryGetValue(MediaTagType.SCSI_MODESENSE_6, out byte[] mode6)) + decMode = Modes.DecodeMode6(mode6, devType); + else if(mediaTags.TryGetValue(MediaTagType.SCSI_MODESENSE_10, out byte[] mode10)) + decMode = Modes.DecodeMode10(mode10, devType); + + byte mediumType = 0; + byte densityCode = 0; + + if(decMode.HasValue) + { + mediumType = (byte)decMode.Value.Header.MediumType; + if(decMode.Value.Header.BlockDescriptors.Length >= 1) + densityCode = (byte)decMode.Value.Header.BlockDescriptors[0].Density; + + foreach(Modes.ModePage page in decMode.Value.Pages) + // CD-ROM page + if(page.Page == 0x2A && page.Subpage == 0) + { + if(mediaTags.ContainsKey(MediaTagType.SCSI_MODEPAGE_2A)) + mediaTags.Remove(MediaTagType.SCSI_MODEPAGE_2A); + mediaTags.Add(MediaTagType.SCSI_MODEPAGE_2A, page.PageResponse); + } + // Rigid Disk page + else if(page.Page == 0x04 && page.Subpage == 0) + { + Modes.ModePage_04? mode04 = Modes.DecodeModePage_04(page.PageResponse); + if(!mode04.HasValue) continue; + + imageInfo.Cylinders = mode04.Value.Cylinders; + imageInfo.Heads = mode04.Value.Heads; + imageInfo.SectorsPerTrack = + (uint)(imageInfo.Sectors / (mode04.Value.Cylinders * mode04.Value.Heads)); + } + // Flexible Disk Page + else if(page.Page == 0x05 && page.Subpage == 0) + { + Modes.ModePage_05? mode05 = Modes.DecodeModePage_05(page.PageResponse); + if(!mode05.HasValue) continue; + + imageInfo.Cylinders = mode05.Value.Cylinders; + imageInfo.Heads = mode05.Value.Heads; + imageInfo.SectorsPerTrack = mode05.Value.SectorsPerTrack; + } + } + + if(scsiInq.HasValue) + { + imageInfo.DriveManufacturer = + VendorString.Prettify(StringHandlers.CToString(scsiInq.Value.VendorIdentification).Trim()); + imageInfo.DriveModel = + StringHandlers.CToString(scsiInq.Value.ProductIdentification).Trim(); + imageInfo.DriveFirmwareRevision = + StringHandlers.CToString(scsiInq.Value.ProductRevisionLevel).Trim(); + imageInfo.MediaType = MediaTypeFromScsi.Get((byte)devType, imageInfo.DriveManufacturer, + imageInfo.DriveModel, mediumType, densityCode, + imageInfo.Sectors, imageInfo.SectorSize); + } + + if(imageInfo.MediaType == MediaType.Unknown) + imageInfo.MediaType = devType == PeripheralDeviceTypes.OpticalDevice + ? MediaType.UnknownMO + : MediaType.GENERIC_HDD; + } + + // It's ATA, check tags + if(mediaTags.TryGetValue(MediaTagType.ATA_IDENTIFY, out byte[] identifyBuf)) + { + Identify.IdentifyDevice? ataId = Decoders.ATA.Identify.Decode(identifyBuf); + if(ataId.HasValue) + { + imageInfo.MediaType = (ushort)ataId.Value.GeneralConfiguration == 0x848A + ? MediaType.CompactFlash + : MediaType.GENERIC_HDD; + + if(ataId.Value.Cylinders == 0 || ataId.Value.Heads == 0 || ataId.Value.SectorsPerTrack == 0) + { + imageInfo.Cylinders = ataId.Value.CurrentCylinders; + imageInfo.Heads = ataId.Value.CurrentHeads; + imageInfo.SectorsPerTrack = ataId.Value.CurrentSectorsPerTrack; + } + else + { + imageInfo.Cylinders = ataId.Value.Cylinders; + imageInfo.Heads = ataId.Value.Heads; + imageInfo.SectorsPerTrack = ataId.Value.SectorsPerTrack; + } + } + } + + switch(imageInfo.MediaType) + { + case MediaType.CD: + case MediaType.DVDPR: + case MediaType.DVDR: + case MediaType.DVDRDL: + case MediaType.DVDPRDL: + case MediaType.BDR: + case MediaType.BDRXL: + imageInfo.XmlMediaType = XmlMediaType.OpticalDisc; + break; + default: + imageInfo.XmlMediaType = XmlMediaType.BlockMedia; + break; + } + + if(imageInfo.XmlMediaType == XmlMediaType.OpticalDisc) + { + imageInfo.HasSessions = true; + imageInfo.HasPartitions = true; + } + + DicConsole.VerboseWriteLine("Raw disk image contains a disk of type {0}", imageInfo.MediaType); + + imageInfo.ReadableMediaTags = new List(mediaTags.Keys); + return true; } @@ -907,7 +1221,9 @@ namespace DiscImageChef.DiscImages public byte[] ReadDiskTag(MediaTagType tag) { - throw new FeatureUnsupportedImageException("Feature not supported by image format"); + if(mediaTags.TryGetValue(tag, out byte[] data)) return data; + + throw new FeatureNotPresentImageException("Requested tag is not present in image"); } public byte[] ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag) @@ -920,8 +1236,8 @@ namespace DiscImageChef.DiscImages throw new FeatureUnsupportedImageException("Feature not supported by image format"); } - // TODO: Support media tags as separate files - public IEnumerable SupportedMediaTags => new MediaTagType[] { }; + public IEnumerable SupportedMediaTags => + readWriteSidecars.Concat(writeOnlySidecars).OrderBy(t => t.tag).Select(t => t.tag).ToArray(); public IEnumerable SupportedSectorTags => new SectorTagType[] { }; public IEnumerable SupportedMediaTypes @@ -989,6 +1305,8 @@ namespace DiscImageChef.DiscImages return false; } + basepath = Path.Combine(Path.GetDirectoryName(path), Path.GetFileNameWithoutExtension(path)); + IsWriting = true; ErrorMessage = null; return true; @@ -1014,9 +1332,16 @@ namespace DiscImageChef.DiscImages public bool WriteMediaTag(byte[] data, MediaTagType tag) { - // TODO: Implement - ErrorMessage = "Writing media tags is not supported."; - return false; + if(!SupportedMediaTags.Contains(tag)) + { + ErrorMessage = $"Tried to write unsupported media tag {tag}."; + return false; + } + + if(mediaTags.ContainsKey(tag)) mediaTags.Remove(tag); + + mediaTags.Add(tag, data); + return true; } public bool WriteSector(byte[] data, ulong sectorAddress) @@ -1105,6 +1430,19 @@ namespace DiscImageChef.DiscImages writingStream.Close(); IsWriting = false; + foreach(KeyValuePair tag in mediaTags) + { + string suffix = readWriteSidecars.Concat(writeOnlySidecars).Where(t => t.tag == tag.Key) + .Select(t => t.name).FirstOrDefault(); + + if(suffix == null) continue; + + FileStream tagStream = + new FileStream(basepath + suffix, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.None); + tagStream.Write(tag.Value, 0, tag.Value.Length); + tagStream.Close(); + } + return true; }