diff --git a/Aaru.Tests/WritableImages/AaruFormat/V1/FromAaru.cs b/Aaru.Tests/WritableImages/AaruFormat/V1/FromAaru.cs new file mode 100644 index 000000000..f0d560664 --- /dev/null +++ b/Aaru.Tests/WritableImages/AaruFormat/V1/FromAaru.cs @@ -0,0 +1,63 @@ +using System.IO; +using Aaru.CommonTypes; +using Aaru.CommonTypes.Interfaces; + +namespace Aaru.Tests.WritableImages.AaruFormat.V1 +{ + public class FromAaru : WritableOpticalMediaImageTest + { + public override string DataFolder => + Path.Combine(Consts.TEST_FILES_ROOT, "Media image formats", "AaruFormat", "V1"); + public override IMediaImage InputPlugin => new DiscImages.AaruFormat(); + public override IWritableImage OutputPlugin => new DiscImages.AaruFormat(); + public override string OutputExtension => "aif"; + public override OpticalImageTestExpected[] Tests => new[] + { + new OpticalImageTestExpected + { + TestFile = "test_multisession.aif", + MediaType = MediaType.CDR, + Sectors = 51168, + SectorSize = 2048, + MD5 = "e2e19cf38891e67a0829d01842b4052e", + LongMD5 = "b31f2d228dd564c88ad851b12b43c01d", + SubchannelMD5 = "989c696ee5bb336b4ad30474da573925", + Tracks = new[] + { + new TrackInfoTestExpected + { + Session = 1, + Start = 0, + End = 8132, + Pregap = 150, + Flags = 4 + }, + new TrackInfoTestExpected + { + Session = 2, + Start = 19383, + End = 25959, + Pregap = 150, + Flags = 4 + }, + new TrackInfoTestExpected + { + Session = 3, + Start = 32710, + End = 38477, + Pregap = 150, + Flags = 4 + }, + new TrackInfoTestExpected + { + Session = 4, + Start = 45228, + End = 51167, + Pregap = 150, + Flags = 4 + } + } + } + }; + } +} \ No newline at end of file diff --git a/Aaru.Tests/WritableImages/Alcohol/FromAaru.cs b/Aaru.Tests/WritableImages/Alcohol/FromAaru.cs new file mode 100644 index 000000000..6d10511df --- /dev/null +++ b/Aaru.Tests/WritableImages/Alcohol/FromAaru.cs @@ -0,0 +1,64 @@ +using System.IO; +using Aaru.CommonTypes; +using Aaru.CommonTypes.Interfaces; +using Aaru.DiscImages; + +namespace Aaru.Tests.WritableImages.Alcohol +{ + public class FromAaru : WritableOpticalMediaImageTest + { + public override string DataFolder => + Path.Combine(Consts.TEST_FILES_ROOT, "Media image formats", "AaruFormat", "V1"); + public override IMediaImage InputPlugin => new DiscImages.AaruFormat(); + public override IWritableImage OutputPlugin => new Alcohol120(); + public override string OutputExtension => "mds"; + public override OpticalImageTestExpected[] Tests => new[] + { + new OpticalImageTestExpected + { + TestFile = "test_multisession.aif", + MediaType = MediaType.CDR, + Sectors = 51168, + SectorSize = 2048, + MD5 = "e2e19cf38891e67a0829d01842b4052e", + LongMD5 = "b31f2d228dd564c88ad851b12b43c01d", + SubchannelMD5 = "989c696ee5bb336b4ad30474da573925", + Tracks = new[] + { + new TrackInfoTestExpected + { + Session = 1, + Start = 0, + End = 8132, + Pregap = 150, + Flags = 4 + }, + new TrackInfoTestExpected + { + Session = 2, + Start = 19383, + End = 25959, + Pregap = 150, + Flags = 4 + }, + new TrackInfoTestExpected + { + Session = 3, + Start = 32710, + End = 38477, + Pregap = 150, + Flags = 4 + }, + new TrackInfoTestExpected + { + Session = 4, + Start = 45228, + End = 51167, + Pregap = 150, + Flags = 4 + } + } + } + }; + } +} \ No newline at end of file diff --git a/Aaru.Tests/WritableImages/BaseWritableMediaImageTest.cs b/Aaru.Tests/WritableImages/BaseWritableMediaImageTest.cs new file mode 100644 index 000000000..be694c5dd --- /dev/null +++ b/Aaru.Tests/WritableImages/BaseWritableMediaImageTest.cs @@ -0,0 +1,12 @@ +using Aaru.CommonTypes.Interfaces; + +namespace Aaru.Tests.WritableImages +{ + public abstract class BaseWritableMediaImageTest + { + public abstract string DataFolder { get; } + public abstract IMediaImage InputPlugin { get; } + public abstract IWritableImage OutputPlugin { get; } + public abstract string OutputExtension { get; } + } +} \ No newline at end of file diff --git a/Aaru.Tests/WritableImages/CDRDAO/FromAaru.cs b/Aaru.Tests/WritableImages/CDRDAO/FromAaru.cs new file mode 100644 index 000000000..57999e840 --- /dev/null +++ b/Aaru.Tests/WritableImages/CDRDAO/FromAaru.cs @@ -0,0 +1,64 @@ +using System.IO; +using Aaru.CommonTypes; +using Aaru.CommonTypes.Interfaces; +using Aaru.DiscImages; + +namespace Aaru.Tests.WritableImages.CDRDAO +{ + public class FromAaru : WritableOpticalMediaImageTest + { + public override string DataFolder => + Path.Combine(Consts.TEST_FILES_ROOT, "Media image formats", "AaruFormat", "V1"); + public override IMediaImage InputPlugin => new DiscImages.AaruFormat(); + public override IWritableImage OutputPlugin => new Cdrdao(); + public override string OutputExtension => "toc"; + public override OpticalImageTestExpected[] Tests => new[] + { + new OpticalImageTestExpected + { + TestFile = "test_multisession.aif", + MediaType = MediaType.CDR, + Sectors = 51168, + SectorSize = 2048, + MD5 = "e2e19cf38891e67a0829d01842b4052e", + LongMD5 = "b31f2d228dd564c88ad851b12b43c01d", + SubchannelMD5 = "989c696ee5bb336b4ad30474da573925", + Tracks = new[] + { + new TrackInfoTestExpected + { + Session = 1, + Start = 0, + End = 8132, + Pregap = 150, + Flags = 4 + }, + new TrackInfoTestExpected + { + Session = 2, + Start = 19383, + End = 25959, + Pregap = 150, + Flags = 4 + }, + new TrackInfoTestExpected + { + Session = 3, + Start = 32710, + End = 38477, + Pregap = 150, + Flags = 4 + }, + new TrackInfoTestExpected + { + Session = 4, + Start = 45228, + End = 51167, + Pregap = 150, + Flags = 4 + } + } + } + }; + } +} \ No newline at end of file diff --git a/Aaru.Tests/WritableImages/CDRWin/FromAaru.cs b/Aaru.Tests/WritableImages/CDRWin/FromAaru.cs new file mode 100644 index 000000000..93165b6a6 --- /dev/null +++ b/Aaru.Tests/WritableImages/CDRWin/FromAaru.cs @@ -0,0 +1,64 @@ +using System.IO; +using Aaru.CommonTypes; +using Aaru.CommonTypes.Interfaces; +using Aaru.DiscImages; + +namespace Aaru.Tests.WritableImages.CDRWin +{ + public class FromAaru : WritableOpticalMediaImageTest + { + public override string DataFolder => + Path.Combine(Consts.TEST_FILES_ROOT, "Media image formats", "AaruFormat", "V1"); + public override IMediaImage InputPlugin => new DiscImages.AaruFormat(); + public override IWritableImage OutputPlugin => new CdrWin(); + public override string OutputExtension => "cue"; + public override OpticalImageTestExpected[] Tests => new[] + { + new OpticalImageTestExpected + { + TestFile = "test_multisession.aif", + MediaType = MediaType.CDR, + Sectors = 51168, + SectorSize = 2048, + MD5 = "e2e19cf38891e67a0829d01842b4052e", + LongMD5 = "b31f2d228dd564c88ad851b12b43c01d", + SubchannelMD5 = "989c696ee5bb336b4ad30474da573925", + Tracks = new[] + { + new TrackInfoTestExpected + { + Session = 1, + Start = 0, + End = 8132, + Pregap = 150, + Flags = 4 + }, + new TrackInfoTestExpected + { + Session = 2, + Start = 19383, + End = 25959, + Pregap = 150, + Flags = 4 + }, + new TrackInfoTestExpected + { + Session = 3, + Start = 32710, + End = 38477, + Pregap = 150, + Flags = 4 + }, + new TrackInfoTestExpected + { + Session = 4, + Start = 45228, + End = 51167, + Pregap = 150, + Flags = 4 + } + } + } + }; + } +} \ No newline at end of file diff --git a/Aaru.Tests/WritableImages/CloneCD/FromAaru.cs b/Aaru.Tests/WritableImages/CloneCD/FromAaru.cs new file mode 100644 index 000000000..585f201b4 --- /dev/null +++ b/Aaru.Tests/WritableImages/CloneCD/FromAaru.cs @@ -0,0 +1,64 @@ +using System.IO; +using Aaru.CommonTypes; +using Aaru.CommonTypes.Interfaces; +using Aaru.DiscImages; + +namespace Aaru.Tests.WritableImages.CloneCD +{ + public class FromAaru : WritableOpticalMediaImageTest + { + public override string DataFolder => + Path.Combine(Consts.TEST_FILES_ROOT, "Media image formats", "AaruFormat", "V1"); + public override IMediaImage InputPlugin => new DiscImages.AaruFormat(); + public override IWritableImage OutputPlugin => new CloneCd(); + public override string OutputExtension => "ccd"; + public override OpticalImageTestExpected[] Tests => new[] + { + new OpticalImageTestExpected + { + TestFile = "test_multisession.aif", + MediaType = MediaType.CDR, + Sectors = 51168, + SectorSize = 2048, + MD5 = "e2e19cf38891e67a0829d01842b4052e", + LongMD5 = "b31f2d228dd564c88ad851b12b43c01d", + SubchannelMD5 = "989c696ee5bb336b4ad30474da573925", + Tracks = new[] + { + new TrackInfoTestExpected + { + Session = 1, + Start = 0, + End = 8132, + Pregap = 150, + Flags = 4 + }, + new TrackInfoTestExpected + { + Session = 2, + Start = 19383, + End = 25959, + Pregap = 150, + Flags = 4 + }, + new TrackInfoTestExpected + { + Session = 3, + Start = 32710, + End = 38477, + Pregap = 150, + Flags = 4 + }, + new TrackInfoTestExpected + { + Session = 4, + Start = 45228, + End = 51167, + Pregap = 150, + Flags = 4 + } + } + } + }; + } +} \ No newline at end of file diff --git a/Aaru.Tests/WritableImages/WritableOpticalMediaImageTest.cs b/Aaru.Tests/WritableImages/WritableOpticalMediaImageTest.cs new file mode 100644 index 000000000..f20851406 --- /dev/null +++ b/Aaru.Tests/WritableImages/WritableOpticalMediaImageTest.cs @@ -0,0 +1,621 @@ +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.Structs; +using Aaru.Core.Media; +using Aaru.Devices; +using FluentAssertions; +using FluentAssertions.Execution; +using NUnit.Framework; + +namespace Aaru.Tests.WritableImages +{ + public abstract class WritableOpticalMediaImageTest : BaseWritableMediaImageTest + { + const uint SECTORS_TO_READ = 256; + public abstract OpticalImageTestExpected[] Tests { get; } + + [Test] + public void Info() + { + Environment.CurrentDirectory = DataFolder; + + Assert.Multiple(() => + { + foreach(OpticalImageTestExpected test in Tests) + { + string testFile = test.TestFile; + + bool exists = File.Exists(testFile); + Assert.True(exists, $"{testFile} not found"); + + // ReSharper disable once ConditionIsAlwaysTrueOrFalse + // It arrives here... + if(!exists) + continue; + + var filtersList = new FiltersList(); + IFilter filter = filtersList.GetFilter(testFile); + filter.Open(testFile); + + var image = Activator.CreateInstance(InputPlugin.GetType()) as IOpticalMediaImage; + Assert.NotNull(image, $"Could not instantiate filesystem for {testFile}"); + + bool opened = image.Open(filter); + Assert.AreEqual(true, opened, $"Open: {testFile}"); + + if(!opened) + continue; + + using(new AssertionScope()) + { + Assert.Multiple(() => + { + Assert.AreEqual(test.Sectors, image.Info.Sectors, $"Sectors: {testFile}"); + + if(test.SectorSize > 0) + Assert.AreEqual(test.SectorSize, image.Info.SectorSize, $"Sector size: {testFile}"); + + Assert.AreEqual(test.MediaType, image.Info.MediaType, $"Media type: {testFile}"); + + if(image.Info.XmlMediaType != XmlMediaType.OpticalDisc) + return; + + Assert.AreEqual(test.Tracks.Length, image.Tracks.Count, $"Tracks: {testFile}"); + + image.Tracks.Select(t => t.TrackSession).Should(). + BeEquivalentTo(test.Tracks.Select(s => s.Session), $"Track session: {testFile}"); + + image.Tracks.Select(t => t.TrackStartSector).Should(). + BeEquivalentTo(test.Tracks.Select(s => s.Start), $"Track start: {testFile}"); + + image.Tracks.Select(t => t.TrackEndSector).Should(). + BeEquivalentTo(test.Tracks.Select(s => s.End), $"Track end: {testFile}"); + + image.Tracks.Select(t => t.TrackPregap).Should(). + BeEquivalentTo(test.Tracks.Select(s => s.Pregap), $"Track pregap: {testFile}"); + + int trackNo = 0; + + byte?[] flags = new byte?[image.Tracks.Count]; + ulong latestEndSector = 0; + + foreach(Track currentTrack in image.Tracks) + { + if(currentTrack.TrackEndSector > latestEndSector) + latestEndSector = currentTrack.TrackEndSector; + + if(image.Info.ReadableSectorTags.Contains(SectorTagType.CdTrackFlags)) + flags[trackNo] = image.ReadSectorTag(currentTrack.TrackSequence, + SectorTagType.CdTrackFlags)[0]; + + trackNo++; + } + + flags.Should().BeEquivalentTo(test.Tracks.Select(s => s.Flags), $"Track flags: {testFile}"); + + Assert.AreEqual(latestEndSector, image.Info.Sectors - 1, + $"Last sector for tracks is {latestEndSector}, but it is {image.Info.Sectors} for image"); + }); + } + } + }); + } + + [Test] + public void Convert() + { + Environment.CurrentDirectory = DataFolder; + + Assert.Multiple(() => + { + foreach(OpticalImageTestExpected test in Tests) + { + string testFile = test.TestFile; + + bool exists = File.Exists(testFile); + Assert.True(exists, $"{testFile} not found"); + + // ReSharper disable once ConditionIsAlwaysTrueOrFalse + // It arrives here... + if(!exists) + continue; + + var filtersList = new FiltersList(); + IFilter filter = filtersList.GetFilter(testFile); + filter.Open(testFile); + + var inputFormat = Activator.CreateInstance(InputPlugin.GetType()) as IOpticalMediaImage; + Assert.NotNull(inputFormat, $"Could not instantiate input plugin for {testFile}"); + + bool opened = inputFormat.Open(filter); + Assert.AreEqual(true, opened, $"Open: {testFile}"); + + if(!opened) + continue; + + string outputPath = + Path.Combine(Path.GetTempPath(), $"{Path.GetRandomFileName()}.{OutputExtension}"); + + var outputFormat = Activator.CreateInstance(OutputPlugin.GetType()) as IWritableOpticalImage; + Assert.NotNull(outputFormat, $"Could not instantiate output plugin for {testFile}"); + + Assert.IsTrue(outputFormat.SupportedMediaTypes.Contains(inputFormat.Info.MediaType), + $"Trying to convert unsupported media type {inputFormat.Info.MediaType} for {testFile}"); + + bool useLong = inputFormat.Info.ReadableSectorTags.Count != 0; + + foreach(SectorTagType sectorTag in inputFormat.Info.ReadableSectorTags.Where(sectorTag => + !outputFormat.SupportedSectorTags.Contains(sectorTag))) + { + if(sectorTag != SectorTagType.CdTrackFlags && + sectorTag != SectorTagType.CdTrackIsrc && + sectorTag != SectorTagType.CdSectorSubchannel) + useLong = false; + } + + Assert.IsTrue(outputFormat.Create(outputPath, inputFormat.Info.MediaType, new Dictionary(), inputFormat.Info.Sectors, inputFormat.Info.SectorSize), + $"Error {outputFormat.ErrorMessage} creating output image."); + + foreach(MediaTagType mediaTag in inputFormat.Info.ReadableMediaTags.Where(mediaTag => + outputFormat.SupportedMediaTags.Contains(mediaTag))) + outputFormat.WriteMediaTag(inputFormat.ReadDiskTag(mediaTag), mediaTag); + + Assert.IsTrue(outputFormat.SetTracks(inputFormat.Tracks), + $"Error {outputFormat.ErrorMessage} sending tracks list to output image."); + + ulong doneSectors = 0; + + foreach(Track track in inputFormat.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 = outputFormat.WriteSectorLong(sector, doneSectors + track.TrackStartSector); + } + else + { + sector = inputFormat.ReadSectorsLong(doneSectors + track.TrackStartSector, + sectorsToDo); + + result = outputFormat.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 = outputFormat.WriteSector(sector, doneSectors + track.TrackStartSector); + } + else + { + sector = inputFormat.ReadSectors(doneSectors + track.TrackStartSector, sectorsToDo); + + result = outputFormat.WriteSectors(sector, doneSectors + track.TrackStartSector, + sectorsToDo); + } + } + + Assert.IsTrue(result, + $"Error {outputFormat.ErrorMessage} writing sector {doneSectors + track.TrackStartSector}..."); + + 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[inputFormat.Tracks.Count]; + + for(int i = 0; i < tracks.Length; i++) + { + tracks[i] = new Track + { + Indexes = new Dictionary(), + TrackDescription = inputFormat.Tracks[i].TrackDescription, + TrackEndSector = inputFormat.Tracks[i].TrackEndSector, + TrackStartSector = inputFormat.Tracks[i].TrackStartSector, + TrackPregap = inputFormat.Tracks[i].TrackPregap, + TrackSequence = inputFormat.Tracks[i].TrackSequence, + TrackSession = inputFormat.Tracks[i].TrackSession, + TrackBytesPerSector = inputFormat.Tracks[i].TrackBytesPerSector, + TrackRawBytesPerSector = inputFormat.Tracks[i].TrackRawBytesPerSector, + TrackType = inputFormat.Tracks[i].TrackType, + TrackSubchannelType = inputFormat.Tracks[i].TrackSubchannelType + }; + + foreach(KeyValuePair idx in inputFormat.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(!outputFormat.SupportedSectorTags.Contains(tag)) + continue; + + foreach(Track track in inputFormat.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 = outputFormat.WriteSectorTag(sector, track.TrackSequence, tag); + + Assert.IsTrue(result, + $"Error {outputFormat.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, outputFormat, true, true, null, null, + smallestPregapLbaPerTrack, false); + + if(indexesChanged) + outputFormat.SetTracks(tracks.ToList()); + + result = true; + } + else + result = + outputFormat.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, outputFormat, true, true, null, null, + smallestPregapLbaPerTrack, false); + + if(indexesChanged) + outputFormat.SetTracks(tracks.ToList()); + + result = true; + } + else + result = + outputFormat.WriteSectorsTag(sector, doneSectors + track.TrackStartSector, + sectorsToDo, tag); + } + + Assert.IsTrue(result, + $"Error {outputFormat.ErrorMessage} writing tag for sector {doneSectors + track.TrackStartSector}, not continuing..."); + + doneSectors += sectorsToDo; + } + } + } + + if(isrcs.Count > 0) + foreach(KeyValuePair isrc in isrcs) + outputFormat.WriteSectorTag(Encoding.UTF8.GetBytes(isrc.Value), isrc.Key, + SectorTagType.CdTrackIsrc); + + if(trackFlags.Count > 0) + foreach((byte track, byte flags) in trackFlags) + outputFormat.WriteSectorTag(new[] + { + flags + }, track, SectorTagType.CdTrackFlags); + + if(mcn != null) + outputFormat.WriteMediaTag(Encoding.UTF8.GetBytes(mcn), MediaTagType.CD_MCN); + + // TODO: Progress + if(inputFormat.Info.MediaType == MediaType.CD || + inputFormat.Info.MediaType == MediaType.CDDA || + inputFormat.Info.MediaType == MediaType.CDG || + inputFormat.Info.MediaType == MediaType.CDEG || + inputFormat.Info.MediaType == MediaType.CDI || + inputFormat.Info.MediaType == MediaType.CDROM || + inputFormat.Info.MediaType == MediaType.CDROMXA || + inputFormat.Info.MediaType == MediaType.CDPLUS || + inputFormat.Info.MediaType == MediaType.CDMO || + inputFormat.Info.MediaType == MediaType.CDR || + inputFormat.Info.MediaType == MediaType.CDRW || + inputFormat.Info.MediaType == MediaType.CDMRW || + inputFormat.Info.MediaType == MediaType.VCD || + inputFormat.Info.MediaType == MediaType.SVCD || + inputFormat.Info.MediaType == MediaType.PCD || + inputFormat.Info.MediaType == MediaType.DTSCD || + inputFormat.Info.MediaType == MediaType.CDMIDI || + inputFormat.Info.MediaType == MediaType.CDV || + inputFormat.Info.MediaType == MediaType.CDIREADY || + inputFormat.Info.MediaType == MediaType.FMTOWNS || + inputFormat.Info.MediaType == MediaType.PS1CD || + inputFormat.Info.MediaType == MediaType.PS2CD || + inputFormat.Info.MediaType == MediaType.MEGACD || + inputFormat.Info.MediaType == MediaType.SATURNCD || + inputFormat.Info.MediaType == MediaType.GDROM || + inputFormat.Info.MediaType == MediaType.GDR || + inputFormat.Info.MediaType == MediaType.MilCD || + inputFormat.Info.MediaType == MediaType.SuperCDROM2 || + inputFormat.Info.MediaType == MediaType.JaguarCD || + inputFormat.Info.MediaType == MediaType.ThreeDO || + inputFormat.Info.MediaType == MediaType.PCFX || + inputFormat.Info.MediaType == MediaType.NeoGeoCD || + inputFormat.Info.MediaType == MediaType.CDTV || + inputFormat.Info.MediaType == MediaType.CD32 || + inputFormat.Info.MediaType == MediaType.Playdia || + inputFormat.Info.MediaType == MediaType.Pippin || + inputFormat.Info.MediaType == MediaType.VideoNow || + inputFormat.Info.MediaType == MediaType.VideoNowColor || + inputFormat.Info.MediaType == MediaType.VideoNowXp || + inputFormat.Info.MediaType == MediaType.CVD) + CompactDisc.GenerateSubchannels(subchannelExtents, tracks, trackFlags, inputFormat.Info.Sectors, + null, null, null, null, null, outputFormat); + + Assert.IsTrue(outputFormat.Close(), + $"Error {outputFormat.ErrorMessage} closing output image... Contents are not correct."); + + filtersList = new FiltersList(); + filter = filtersList.GetFilter(outputPath); + filter.Open(outputPath); + + string? tmpFolder = Path.GetDirectoryName(outputPath); + Environment.CurrentDirectory = tmpFolder; + + var image = Activator.CreateInstance(OutputPlugin.GetType()) as IOpticalMediaImage; + Assert.NotNull(image, $"Could not instantiate output plugin for {testFile}"); + + opened = image.Open(filter); + Assert.AreEqual(true, opened, $"Open created: {testFile}"); + + if(!opened) + continue; + + using(new AssertionScope()) + { + Assert.Multiple(() => + { + Assert.AreEqual(test.Sectors, image.Info.Sectors, $"Sectors (output): {testFile}"); + + if(test.SectorSize > 0) + Assert.AreEqual(test.SectorSize, image.Info.SectorSize, + $"Sector size (output): {testFile}"); + + Assert.AreEqual(test.Tracks.Length, image.Tracks.Count, $"Tracks (output): {testFile}"); + + image.Tracks.Select(t => t.TrackSession).Should(). + BeEquivalentTo(test.Tracks.Select(s => s.Session), + $"Track session (output): {testFile}"); + + image.Tracks.Select(t => t.TrackStartSector).Should(). + BeEquivalentTo(test.Tracks.Select(s => s.Start), $"Track start (output): {testFile}"); + + image.Tracks.Select(t => t.TrackEndSector).Should(). + BeEquivalentTo(test.Tracks.Select(s => s.End), $"Track end (output): {testFile}"); + + image.Tracks.Select(t => t.TrackPregap).Should(). + BeEquivalentTo(test.Tracks.Select(s => s.Pregap), + $"Track pregap (output): {testFile}"); + + int trackNo = 0; + + byte?[] flags = new byte?[image.Tracks.Count]; + ulong latestEndSector = 0; + + foreach(Track currentTrack in image.Tracks) + { + if(currentTrack.TrackEndSector > latestEndSector) + latestEndSector = currentTrack.TrackEndSector; + + if(image.Info.ReadableSectorTags.Contains(SectorTagType.CdTrackFlags)) + flags[trackNo] = image.ReadSectorTag(currentTrack.TrackSequence, + SectorTagType.CdTrackFlags)[0]; + + trackNo++; + } + + flags.Should().BeEquivalentTo(test.Tracks.Select(s => s.Flags), + $"Track flags (output): {testFile}"); + + Assert.AreEqual(latestEndSector, image.Info.Sectors - 1, + $"Last sector for tracks is {latestEndSector}, but it is {image.Info.Sectors} for image (output)"); + }); + } + + Md5Context ctx; + + foreach(bool @long in new[] + { + /*false,*/ true + }) + { + ctx = new Md5Context(); + + foreach(Track currentTrack in image.Tracks) + { + ulong sectors = currentTrack.TrackEndSector - currentTrack.TrackStartSector + 1; + doneSectors = 0; + + while(doneSectors < sectors) + { + byte[] sector; + + if(sectors - doneSectors >= SECTORS_TO_READ) + { + sector = + @long ? image.ReadSectorsLong(doneSectors, SECTORS_TO_READ, + currentTrack.TrackSequence) + : image.ReadSectors(doneSectors, SECTORS_TO_READ, + currentTrack.TrackSequence); + + doneSectors += SECTORS_TO_READ; + } + else + { + sector = + @long ? image.ReadSectorsLong(doneSectors, (uint)(sectors - doneSectors), + currentTrack.TrackSequence) + : image.ReadSectors(doneSectors, (uint)(sectors - doneSectors), + currentTrack.TrackSequence); + + doneSectors += sectors - doneSectors; + } + + ctx.Update(sector); + } + } + + Assert.AreEqual(@long ? test.LongMD5 : test.MD5, ctx.End(), + $"{(@long ? "Long hash (output)" : "Hash (output)")}: {testFile}"); + } + + if(!image.Info.ReadableSectorTags.Contains(SectorTagType.CdSectorSubchannel)) + return; + + ctx = new Md5Context(); + + foreach(Track currentTrack in image.Tracks) + { + ulong sectors = currentTrack.TrackEndSector - currentTrack.TrackStartSector + 1; + doneSectors = 0; + + while(doneSectors < sectors) + { + byte[] sector; + + if(sectors - doneSectors >= SECTORS_TO_READ) + { + sector = image.ReadSectorsTag(doneSectors, SECTORS_TO_READ, currentTrack.TrackSequence, + SectorTagType.CdSectorSubchannel); + + doneSectors += SECTORS_TO_READ; + } + else + { + sector = image.ReadSectorsTag(doneSectors, (uint)(sectors - doneSectors), + currentTrack.TrackSequence, + SectorTagType.CdSectorSubchannel); + + doneSectors += sectors - doneSectors; + } + + ctx.Update(sector); + } + } + + Assert.AreEqual(test.SubchannelMD5, ctx.End(), $"Subchannel hash (output): {testFile}"); + } + }); + } + } +} \ No newline at end of file