diff --git a/Aaru.Tests/Issues/257.cs b/Aaru.Tests/Issues/257.cs new file mode 100644 index 000000000..2421c3705 --- /dev/null +++ b/Aaru.Tests/Issues/257.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; +using System.IO; +using Aaru.CommonTypes.Interfaces; +using Aaru.DiscImages; + +namespace Aaru.Tests.Issues +{ + /* SilasLaspada commented on Nov 23, 2019 + * + * Trying to convert an NRG image to various images formats fails in various ways. Converting it to ISO prints + * "Converting sectors X to Y in track 1 (XX.XX% done)Error Writing sectors with tags is not supported. writing + * sector X, continuing..." several times. Converting to MDS doesn't result in any visible errors in the log, but + * DIC is unable to identify the file system in the resulting image. Converting to DICF prints "Converting sectors + * 0 to 64 in track 1 (0.00% done)Error Incorrect data size writing sector 0, not continuing..." once before + * stopping. DIC is able to extract the files from the original image so it seems to be good. + */ + + // 20200311 CLAUNIA: Fixed in f4a1c28feabb50a43036944cf7f6c028eb6f8b93 + // 20200621 CLAUNIA: Fixed in c80baa5efb4ea8a9e4347278086b2414469ae4c6 + public class _257 : OpticalImageConvertIssueTest + { + public override Dictionary ParsedOptions => new Dictionary(); + public override string DataFolder => Path.Combine(Consts.TEST_FILES_ROOT, "Issues", "Fixed", "issue257"); + public override string InputPath => "TempImage.nrg.xz"; + public override string SuggestedOutputFilename => "AaruIssue257Output.iso"; + public override IWritableImage OutputFormat => new ZZZRawImage(); + public override string Md5 => "51303b30b4e4896c01d47af9b5c0d5b5"; + public override bool UseLong => false; + } +} \ No newline at end of file diff --git a/Aaru.Tests/Issues/OpticalImageConvertIssueTest.cs b/Aaru.Tests/Issues/OpticalImageConvertIssueTest.cs new file mode 100644 index 000000000..f77428aca --- /dev/null +++ b/Aaru.Tests/Issues/OpticalImageConvertIssueTest.cs @@ -0,0 +1,387 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using Aaru.Checksums; +using Aaru.CommonTypes; +using Aaru.CommonTypes.Enums; +using Aaru.CommonTypes.Interfaces; +using Aaru.CommonTypes.Metadata; +using Aaru.CommonTypes.Structs; +using Aaru.Console; +using Aaru.Core; +using Aaru.Core.Media; +using Aaru.Devices; +using NUnit.Framework; +using Schemas; +using ImageInfo = Aaru.CommonTypes.Structs.ImageInfo; +using Version = Aaru.CommonTypes.Interop.Version; + +namespace Aaru.Tests.Issues +{ + /// + /// This tests the conversion of an input image (autodetected) to an output image and checks that the resulting + /// image has the same hash as it should + /// + + // TODO: The algorithm should be in Core, not copied in 3 places + public abstract class OpticalImageConvertIssueTest + { + public const int SECTORS_TO_READ = 256; + public abstract Dictionary ParsedOptions { get; } + public abstract string DataFolder { get; } + public abstract string InputPath { get; } + public abstract string SuggestedOutputFilename { get; } + public abstract IWritableImage OutputFormat { get; } + public abstract string Md5 { get; } + public abstract bool UseLong { get; } + + [Test] + public void Convert() + { + Environment.CurrentDirectory = DataFolder; + + Resume resume = null; + CICMMetadataType sidecar = null; + + var filtersList = new FiltersList(); + IFilter inputFilter = filtersList.GetFilter(InputPath); + + Assert.IsNotNull(inputFilter, "Cannot open specified file."); + + string outputPath = Path.Combine(Path.GetTempPath(), SuggestedOutputFilename); + + Assert.IsFalse(File.Exists(outputPath), "Output file already exists, not continuing."); + + IMediaImage inputFormat = ImageFormat.Detect(inputFilter); + + Assert.IsNotNull(inputFormat, "Input image format not identified, not proceeding with conversion."); + + Assert.IsTrue(inputFormat.Open(inputFilter), "Unable to open image format"); + + Assert.IsTrue(OutputFormat.SupportedMediaTypes.Contains(inputFormat.Info.MediaType), + "Output format does not support media type, cannot continue..."); + + if(inputFormat.Info.ReadableSectorTags.Count == 0) + { + Assert.IsFalse(UseLong, "Input image does not support long sectors."); + } + + var inputOptical = inputFormat as IOpticalMediaImage; + var outputOptical = OutputFormat as IWritableOpticalImage; + + Assert.IsNotNull(inputOptical, "Could not treat existing image as optical disc."); + Assert.IsNotNull(outputOptical, "Could not treat new image as optical disc."); + Assert.IsNotNull(inputOptical.Tracks, "Existing image contains no tracks."); + + Assert.IsTrue(outputOptical.Create(outputPath, inputFormat.Info.MediaType, ParsedOptions, inputFormat.Info.Sectors, inputFormat.Info.SectorSize), + $"Error {outputOptical.ErrorMessage} creating output image."); + + var metadata = new ImageInfo + { + Application = "Aaru", + ApplicationVersion = Version.GetVersion(), + Comments = inputFormat.Info.Comments, + Creator = inputFormat.Info.Creator, + DriveFirmwareRevision = inputFormat.Info.DriveFirmwareRevision, + DriveManufacturer = inputFormat.Info.DriveManufacturer, + DriveModel = inputFormat.Info.DriveModel, + DriveSerialNumber = inputFormat.Info.DriveSerialNumber, + LastMediaSequence = inputFormat.Info.LastMediaSequence, + MediaBarcode = inputFormat.Info.MediaBarcode, + MediaManufacturer = inputFormat.Info.MediaManufacturer, + MediaModel = inputFormat.Info.MediaModel, + MediaPartNumber = inputFormat.Info.MediaPartNumber, + MediaSequence = inputFormat.Info.MediaSequence, + MediaSerialNumber = inputFormat.Info.MediaSerialNumber, + MediaTitle = inputFormat.Info.MediaTitle + }; + + Assert.IsTrue(outputOptical.SetMetadata(metadata), + $"Error {outputOptical.ErrorMessage} setting metadata, "); + + CICMMetadataType cicmMetadata = inputFormat.CicmMetadata; + List dumpHardware = inputFormat.DumpHardware; + + foreach(MediaTagType mediaTag in inputFormat.Info.ReadableMediaTags.Where(mediaTag => + outputOptical.SupportedMediaTags.Contains(mediaTag))) + { + AaruConsole.WriteLine("Converting media tag {0}", mediaTag); + byte[] tag = inputFormat.ReadDiskTag(mediaTag); + + Assert.IsTrue(outputOptical.WriteMediaTag(tag, mediaTag)); + } + + AaruConsole.WriteLine("{0} sectors to convert", inputFormat.Info.Sectors); + ulong doneSectors; + + Assert.IsTrue(outputOptical.SetTracks(inputOptical.Tracks), + $"Error {outputOptical.ErrorMessage} sending tracks list to output image."); + + foreach(Track track in inputOptical.Tracks) + { + doneSectors = 0; + ulong trackSectors = track.TrackEndSector - track.TrackStartSector + 1; + + while(doneSectors < trackSectors) + { + byte[] sector; + + uint sectorsToDo; + + if(trackSectors - doneSectors >= SECTORS_TO_READ) + sectorsToDo = SECTORS_TO_READ; + else + sectorsToDo = (uint)(trackSectors - doneSectors); + + bool useNotLong = false; + bool result = false; + + if(UseLong) + { + if(sectorsToDo == 1) + { + sector = inputFormat.ReadSectorLong(doneSectors + track.TrackStartSector); + result = outputOptical.WriteSectorLong(sector, doneSectors + track.TrackStartSector); + } + else + { + sector = inputFormat.ReadSectorsLong(doneSectors + track.TrackStartSector, sectorsToDo); + + result = outputOptical.WriteSectorsLong(sector, doneSectors + track.TrackStartSector, + sectorsToDo); + } + + if(!result && + sector.Length % 2352 != 0) + useNotLong = true; + } + + if(!UseLong || useNotLong) + { + if(sectorsToDo == 1) + { + sector = inputFormat.ReadSector(doneSectors + track.TrackStartSector); + result = outputOptical.WriteSector(sector, doneSectors + track.TrackStartSector); + } + else + { + sector = inputFormat.ReadSectors(doneSectors + track.TrackStartSector, sectorsToDo); + + result = outputOptical.WriteSectors(sector, doneSectors + track.TrackStartSector, + sectorsToDo); + } + } + + Assert.IsTrue(result, + $"Error {outputOptical.ErrorMessage} writing sector {doneSectors + track.TrackStartSector}, not continuing..."); + + doneSectors += sectorsToDo; + } + } + + Dictionary isrcs = new Dictionary(); + Dictionary trackFlags = new Dictionary(); + string mcn = null; + HashSet subchannelExtents = new HashSet(); + Dictionary smallestPregapLbaPerTrack = new Dictionary(); + Track[] tracks = new Track[inputOptical.Tracks.Count]; + + for(int i = 0; i < tracks.Length; i++) + { + tracks[i] = new Track + { + Indexes = new Dictionary(), + TrackDescription = inputOptical.Tracks[i].TrackDescription, + TrackEndSector = inputOptical.Tracks[i].TrackEndSector, + TrackStartSector = inputOptical.Tracks[i].TrackStartSector, + TrackPregap = inputOptical.Tracks[i].TrackPregap, + TrackSequence = inputOptical.Tracks[i].TrackSequence, + TrackSession = inputOptical.Tracks[i].TrackSession, + TrackBytesPerSector = inputOptical.Tracks[i].TrackBytesPerSector, + TrackRawBytesPerSector = inputOptical.Tracks[i].TrackRawBytesPerSector, + TrackType = inputOptical.Tracks[i].TrackType, + TrackSubchannelType = inputOptical.Tracks[i].TrackSubchannelType + }; + + foreach(KeyValuePair idx in inputOptical.Tracks[i].Indexes) + tracks[i].Indexes[idx.Key] = idx.Value; + } + + foreach(SectorTagType tag in inputFormat.Info.ReadableSectorTags.Where(t => t == SectorTagType.CdTrackIsrc). + OrderBy(t => t)) + { + foreach(Track track in tracks) + { + byte[] isrc = inputFormat.ReadSectorTag(track.TrackSequence, tag); + + if(isrc is null) + continue; + + isrcs[(byte)track.TrackSequence] = Encoding.UTF8.GetString(isrc); + } + } + + foreach(SectorTagType tag in inputFormat.Info.ReadableSectorTags. + Where(t => t == SectorTagType.CdTrackFlags).OrderBy(t => t)) + { + foreach(Track track in tracks) + { + byte[] flags = inputFormat.ReadSectorTag(track.TrackSequence, tag); + + if(flags is null) + continue; + + trackFlags[(byte)track.TrackSequence] = flags[0]; + } + } + + for(ulong s = 0; s < inputFormat.Info.Sectors; s++) + { + if(s > int.MaxValue) + break; + + subchannelExtents.Add((int)s); + } + + foreach(SectorTagType tag in inputFormat.Info.ReadableSectorTags.OrderBy(t => t).TakeWhile(tag => UseLong)) + { + switch(tag) + { + case SectorTagType.AppleSectorTag: + case SectorTagType.CdSectorSync: + case SectorTagType.CdSectorHeader: + case SectorTagType.CdSectorSubHeader: + case SectorTagType.CdSectorEdc: + case SectorTagType.CdSectorEccP: + case SectorTagType.CdSectorEccQ: + case SectorTagType.CdSectorEcc: + // This tags are inline in long sector + continue; + } + + if(!outputOptical.SupportedSectorTags.Contains(tag)) + continue; + + foreach(Track track in inputOptical.Tracks) + { + doneSectors = 0; + ulong trackSectors = track.TrackEndSector - track.TrackStartSector + 1; + byte[] sector; + bool result; + + switch(tag) + { + case SectorTagType.CdTrackFlags: + case SectorTagType.CdTrackIsrc: + sector = inputFormat.ReadSectorTag(track.TrackSequence, tag); + result = outputOptical.WriteSectorTag(sector, track.TrackSequence, tag); + + Assert.IsTrue(result, $"Error {outputOptical.ErrorMessage} writing tag, not continuing..."); + + continue; + } + + while(doneSectors < trackSectors) + { + uint sectorsToDo; + + if(trackSectors - doneSectors >= SECTORS_TO_READ) + sectorsToDo = SECTORS_TO_READ; + else + sectorsToDo = (uint)(trackSectors - doneSectors); + + if(sectorsToDo == 1) + { + sector = inputFormat.ReadSectorTag(doneSectors + track.TrackStartSector, tag); + + if(tag == SectorTagType.CdSectorSubchannel) + { + bool indexesChanged = CompactDisc.WriteSubchannelToImage(MmcSubchannel.Raw, + MmcSubchannel.Raw, sector, doneSectors + track.TrackStartSector, 1, null, + isrcs, (byte)track.TrackSequence, ref mcn, tracks, subchannelExtents, true, + outputOptical, true, true, null, null, smallestPregapLbaPerTrack, false); + + if(indexesChanged) + outputOptical.SetTracks(tracks.ToList()); + + result = true; + } + else + result = outputOptical.WriteSectorTag(sector, doneSectors + track.TrackStartSector, + tag); + } + else + { + sector = inputFormat.ReadSectorsTag(doneSectors + track.TrackStartSector, sectorsToDo, tag); + + if(tag == SectorTagType.CdSectorSubchannel) + { + bool indexesChanged = CompactDisc.WriteSubchannelToImage(MmcSubchannel.Raw, + MmcSubchannel.Raw, sector, doneSectors + track.TrackStartSector, sectorsToDo, + null, isrcs, (byte)track.TrackSequence, ref mcn, tracks, subchannelExtents, + true, outputOptical, true, true, null, null, smallestPregapLbaPerTrack, false); + + if(indexesChanged) + outputOptical.SetTracks(tracks.ToList()); + + result = true; + } + else + result = outputOptical.WriteSectorsTag(sector, doneSectors + track.TrackStartSector, + sectorsToDo, tag); + } + + Assert.IsTrue(result, + $"Error {outputOptical.ErrorMessage} writing tag for sector {doneSectors + track.TrackStartSector}, not continuing..."); + + doneSectors += sectorsToDo; + } + } + } + + if(isrcs.Count > 0) + foreach(KeyValuePair isrc in isrcs) + outputOptical.WriteSectorTag(Encoding.UTF8.GetBytes(isrc.Value), isrc.Key, + SectorTagType.CdTrackIsrc); + + if(trackFlags.Count > 0) + foreach((byte track, byte flags) in trackFlags) + outputOptical.WriteSectorTag(new[] + { + flags + }, track, SectorTagType.CdTrackFlags); + + if(mcn != null) + outputOptical.WriteMediaTag(Encoding.UTF8.GetBytes(mcn), MediaTagType.CD_MCN); + + if(resume != null || + dumpHardware != null) + { + if(resume != null) + outputOptical.SetDumpHardware(resume.Tries); + else if(dumpHardware != null) + outputOptical.SetDumpHardware(dumpHardware); + } + + if(sidecar != null || + cicmMetadata != null) + { + if(sidecar != null) + outputOptical.SetCicmMetadata(sidecar); + else if(cicmMetadata != null) + outputOptical.SetCicmMetadata(cicmMetadata); + } + + Assert.True(outputOptical.Close(), + $"Error {outputOptical.ErrorMessage} closing output image... Contents are not correct."); + + string md5 = Md5Context.File(outputPath, out _); + + Assert.AreEqual(Md5, md5, "Hashes are different"); + + File.Delete(outputPath); + } + } +} \ No newline at end of file